@@ -303,17 +303,20 @@ public string ToJSON()
303303 }
304304 else if ( isAnEnum )
305305 {
306- paramObject [ "type" ] = "enum" ;
307-
306+ var subtype = "any" ;
308307 var attribute = p . Attributes ? . Where ( a => a . AttributeClass ? . Name == "YarnEnumParameterAttribute" ) . First ( ) ;
309308 if ( attribute != null && attribute . ConstructorArguments . Count ( ) > 0 )
310309 {
311310 var enumType = attribute . ConstructorArguments [ 0 ] ;
312311 if ( enumType . Type ? . SpecialType == SpecialType . System_String )
313312 {
314- paramObject [ " subtype" ] = enumType . Value as string ?? p . YarnTypeString ;
313+ subtype = enumType . Value as string ?? p . YarnTypeString ;
315314 }
316315 }
316+
317+ paramObject [ "type" ] = "enum" ;
318+ paramObject [ "subtype" ] = subtype ;
319+
317320 }
318321 else
319322 {
@@ -339,8 +342,9 @@ public string ToJSON()
339342 return Yarn . Unity . Editor . Json . Serialize ( result ) ;
340343 }
341344
342- public List < Microsoft . CodeAnalysis . Diagnostic > Validate ( Compilation compilation )
345+ public List < Microsoft . CodeAnalysis . Diagnostic > Validate ( Compilation compilation , ILogger ? logger )
343346 {
347+ logger ? . WriteLine ( $ "Beginning validation") ;
344348 var diagnostics = new List < Microsoft . CodeAnalysis . Diagnostic > ( ) ;
345349 if ( this . MethodDeclarationSyntax == null )
346350 {
@@ -432,11 +436,11 @@ public string ToJSON()
432436 break ;
433437
434438 case ActionType . Command :
435- diagnostics . AddRange ( ValidateCommand ( compilation ) ) ;
439+ diagnostics . AddRange ( ValidateCommand ( compilation , logger ) ) ;
436440 break ;
437441
438442 case ActionType . Function :
439- diagnostics . AddRange ( ValidateFunction ( compilation ) ) ;
443+ diagnostics . AddRange ( ValidateFunction ( compilation , logger ) ) ;
440444 break ;
441445
442446 default :
@@ -447,9 +451,8 @@ public string ToJSON()
447451 return diagnostics ;
448452 }
449453
450- private IEnumerable < Diagnostic > ValidateFunction ( Compilation compilation )
454+ private IEnumerable < Diagnostic > ValidateFunction ( Compilation compilation , ILogger ? logger )
451455 {
452-
453456 string identifier ;
454457 Location returnTypeLocation ;
455458 Location identifierLocation ;
@@ -484,6 +487,12 @@ private IEnumerable<Diagnostic> ValidateFunction(Compilation compilation)
484487 yield return Diagnostic . Create ( Diagnostics . YS1006YarnFunctionsMustBeStatic , identifierLocation ) ;
485488 }
486489
490+ var paramDiags = ValidateParameters ( compilation , logger ) ;
491+ foreach ( var p in paramDiags )
492+ {
493+ yield return p ;
494+ }
495+
487496 // Functions must return a number, string, or bool
488497 var returnTypeSymbol = this . MethodSymbol . ReturnType ;
489498
@@ -510,7 +519,135 @@ private IEnumerable<Diagnostic> ValidateFunction(Compilation compilation)
510519 }
511520 }
512521
513- private IEnumerable < Diagnostic > ValidateCommand ( Compilation compilation )
522+ // validates the parameters are correct
523+ private List < Diagnostic > ValidateParameters ( Compilation compilation , ILogger ? logger )
524+ {
525+ List < Diagnostic > diagnostics = new List < Diagnostic > ( ) ;
526+ ParameterListSyntax ? parameterList = null ;
527+ Location ? actionLocation = null ;
528+ string ? identifier = null ;
529+
530+ if ( this . MethodDeclarationSyntax is MethodDeclarationSyntax methodDeclaration )
531+ {
532+ identifier = methodDeclaration . Identifier . ToString ( ) ;
533+ logger ? . WriteLine ( $ "Validating { identifier } as a method") ;
534+ parameterList = methodDeclaration . ParameterList ;
535+ actionLocation = methodDeclaration . GetLocation ( ) ;
536+ }
537+ else if ( this . MethodDeclarationSyntax is LocalFunctionStatementSyntax localFunctionStatement )
538+ {
539+ identifier = localFunctionStatement . Identifier . ToString ( ) ;
540+ logger ? . WriteLine ( $ "Validating { identifier } as a local function") ;
541+ parameterList = localFunctionStatement . ParameterList ;
542+ actionLocation = localFunctionStatement . GetLocation ( ) ;
543+ }
544+ else if ( this . MethodDeclarationSyntax is LambdaExpressionSyntax lambdaExpression )
545+ {
546+ logger ? . WriteLine ( "The action is a lambda." ) ;
547+ actionLocation = lambdaExpression . GetLocation ( ) ;
548+
549+ if ( lambdaExpression is SimpleLambdaExpressionSyntax )
550+ {
551+ logger ? . WriteLine ( "The action is a simple lambda, validations do not apply here, skipping this action." ) ;
552+ diagnostics . Add ( Diagnostic . Create ( Diagnostics . YS1012ActionIsALambda , actionLocation ) ) ;
553+ return diagnostics ;
554+ }
555+
556+ if ( lambdaExpression is ParenthesizedLambdaExpressionSyntax pls )
557+ {
558+ logger ? . WriteLine ( "The action is a parenthesized lambda, can perform some validation." ) ;
559+
560+ identifier = "(lambda expression)" ;
561+ parameterList = pls . ParameterList ;
562+
563+ diagnostics . Add ( Diagnostic . Create ( Diagnostics . YS1012ActionIsALambda , actionLocation ) ) ;
564+ }
565+ }
566+
567+ if ( parameterList == null || parameterList . Parameters . Count ( ) == 0 )
568+ {
569+ logger ? . WriteLine ( $ "{ identifier } has no parameters, ignoring") ;
570+ return diagnostics ;
571+ }
572+
573+ logger ? . WriteLine ( $ "Will be checking { parameterList . Parameters . Count ( ) } parameters") ;
574+ foreach ( var parameter in parameterList . Parameters )
575+ {
576+ if ( parameter . Type == null )
577+ {
578+ logger ? . WriteLine ( $ "{ parameter . ToFullString ( ) } has no type, ignoring validation?") ;
579+ continue ;
580+ }
581+
582+ var model = compilation . GetSemanticModel ( parameter . SyntaxTree ) ;
583+ var typeInfo = model . GetTypeInfo ( parameter . Type ) . Type ;
584+
585+ var parameterName = model . GetDeclaredSymbol ( parameter ) ? . Name ?? "(UNKNOWN)" ;
586+ logger ? . WriteLine ( $ "Validating { parameterName } ") ;
587+
588+ if ( typeInfo == null )
589+ {
590+ logger ? . WriteLine ( $ "Unable to determine typeinfo of { parameterName } ignoring validation?") ;
591+ continue ;
592+ }
593+
594+ var symbol = model . GetDeclaredSymbol ( parameter ) ;
595+ if ( symbol == null )
596+ {
597+ logger ? . WriteLine ( $ "Unable to determine the declared symbol for { parameterName } , skipping validation") ;
598+ continue ;
599+ }
600+
601+ if ( symbol . IsParams )
602+ {
603+ if ( symbol . Type is IArrayTypeSymbol arrayTypeSymbol )
604+ {
605+ var subtype = arrayTypeSymbol . ElementType ;
606+ logger ? . WriteLine ( $ "{ parameterName } is a params array made up of { subtype } :_{ subtype . Name } _:-{ subtype . GetYarnTypeString ( ) } -") ;
607+ if ( subtype . GetYarnTypeString ( ) == "any" )
608+ {
609+ logger ? . WriteLine ( $ "{ parameterName } is a parameter array of non Yarn compatible types!") ;
610+ diagnostics . Add ( Diagnostic . Create ( Diagnostics . YS1008ActionsParamsArraysMustBeOfYarnTypes , parameter . GetLocation ( ) , parameterName , subtype . Name ) ) ;
611+ }
612+ }
613+ }
614+ else
615+ {
616+ if ( typeInfo . GetYarnTypeString ( ) == "any" && typeInfo . BaseType ? . Name != "Component" )
617+ {
618+ // we have an invalid type
619+ logger ? . WriteLine ( $ "{ parameterName } is an invalid type for use in a Yarn action") ;
620+ diagnostics . Add ( Diagnostic . Create ( Diagnostics . YS1011ActionsParameterIsAnIncompatibleType , parameter . GetLocation ( ) , parameterName , typeInfo . Name ) ) ;
621+ }
622+ }
623+
624+
625+ foreach ( var attribute in symbol . GetAttributes ( ) )
626+ {
627+ // this attribute is an enum parameter
628+ if ( attribute . AttributeClass ? . Name == "YarnEnumParameterAttribute" )
629+ {
630+ if ( typeInfo . GetYarnTypeString ( ) == "any" )
631+ {
632+ logger ? . WriteLine ( $ "{ parameterName } is attributed as an enum but isn't a Yarn compatible type!") ;
633+ diagnostics . Add ( Diagnostic . Create ( Diagnostics . YS1009ActionsEnumAttributedParameterIsOfIncompatibleType , parameter . GetLocation ( ) , parameterName , typeInfo . Name ) ) ;
634+ }
635+ }
636+ if ( attribute . AttributeClass ? . Name == "YarnNodeParameterAttribute" )
637+ {
638+ if ( typeInfo . GetYarnTypeString ( ) != "string" )
639+ {
640+ logger ? . WriteLine ( $ "{ parameterName } is attributed as a node but isn't a string!") ;
641+ diagnostics . Add ( Diagnostic . Create ( Diagnostics . YS1010ActionsNodeAttributedParameterIsOfIncompatibleType , parameter . GetLocation ( ) , parameterName , typeInfo . Name ) ) ;
642+ }
643+ }
644+ }
645+ }
646+
647+ return diagnostics ;
648+ }
649+
650+ private IEnumerable < Diagnostic > ValidateCommand ( Compilation compilation , ILogger ? logger )
514651 {
515652 if ( MethodSymbol == null )
516653 {
@@ -571,6 +708,11 @@ private IEnumerable<Diagnostic> ValidateCommand(Compilation compilation)
571708 throw new InvalidOperationException ( $ "Expected decl for { this . Name } ({ this . SourceFileName } ) was of unexpected type { this . MethodDeclarationSyntax ? . GetType ( ) . Name ?? "null" } ") ;
572709 }
573710
711+ var paramDiags = ValidateParameters ( compilation , logger ) ;
712+ foreach ( var p in paramDiags )
713+ {
714+ yield return p ;
715+ }
574716
575717 var typeIsKnownValid = validCommandReturnTypes . Contains ( returnTypeSymbol )
576718 || validTaskTypes . Contains ( returnTypeSymbol ) ;
0 commit comments