@@ -313,14 +313,6 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
313313 {
314314 var isValid = true ;
315315
316- #if DEBUG
317- // Add Experimenal namespaces in Debug compile if it has not been added in allow list
318- if ( ! settings . AllowPowerFxNamespaces . Contains ( NAMESPACE_PREVIEW ) )
319- {
320- settings . AllowPowerFxNamespaces . Add ( NAMESPACE_PREVIEW ) ;
321- }
322- #endif
323-
324316#if RELEASE
325317 // Add Deprecated namespaces in Release compile if it has not been added in deny list
326318 if ( ! settings . DenyPowerFxNamespaces . Contains ( NAMESPACE_DEPRECATED ) )
@@ -334,22 +326,29 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
334326 stream . Position = 0 ;
335327 ModuleDefinition module = ModuleDefinition . ReadModule ( stream ) ;
336328
337- // Check if PauseModule exists and inspect its IsPreviewNamespaceEnabled property
338- var pauseModule = module . Types . FirstOrDefault ( t => t . Name == "PauseModule" ) ;
339- if ( pauseModule != null )
340- {
341- // Check if the PauseModule has IsPreviewNamespaceEnabled property
342- var previewProperty = pauseModule . Properties . FirstOrDefault ( p => p . Name == "IsPreviewNamespaceEnabled" ) ;
343- if ( previewProperty != null )
344- {
345- // If PauseModule has IsPreviewNamespaceEnabled property, enable Preview namespace
346- // The property's value will be determined at runtime based on YAML settings
347- settings . AllowPowerFxNamespaces . Add ( NAMESPACE_PREVIEW ) ;
348- Logger ? . LogInformation ( "Auto-enabled Preview namespace due to PauseModule.IsPreviewNamespaceEnabled property." ) ;
349- }
350- }
351-
352- // Get the source code of the assembly as will be used to check Power FX Namespaces
329+ // Detect if this assembly contains provider types so we can allow Preview for provider assemblies
330+ var assemblyHasProvider = module . GetAllTypes ( ) . Any ( t =>
331+ t . Interfaces . Any ( i => i . InterfaceType . FullName == typeof ( Providers . ITestWebProvider ) . FullName ) ||
332+ t . Interfaces . Any ( i => i . InterfaceType . FullName == typeof ( Users . IUserManager ) . FullName ) ||
333+ t . Interfaces . Any ( i => i . InterfaceType . FullName == typeof ( Config . IUserCertificateProvider ) . FullName )
334+ ) ;
335+
336+ // Check if PauseModule exists and inspect its IsPreviewNamespaceEnabled property
337+ var pauseModule = module . Types . FirstOrDefault ( t => t . Name == "PauseModule" ) ; // Local flag indicating PauseModule declares IsPreviewNamespaceEnabled
338+ if ( pauseModule != null )
339+ {
340+ // Check if the PauseModule has IsPreviewNamespaceEnabled property
341+ var previewProperty = pauseModule . Properties . FirstOrDefault ( p => p . Name == "IsPreviewNamespaceEnabled" ) ;
342+ if ( previewProperty != null )
343+ {
344+ // Do not modify the global settings here. Instead record that PauseModule exposes the preview toggle.
345+ // The property's value will be determined at runtime based on YAML settings; use the flag to
346+ // selectively allow the Preview namespace for providers only.
347+ Logger ? . LogInformation ( "Detected PauseModule.IsPreviewNamespaceEnabled; preview semantics will be applied per-type." ) ;
348+ }
349+ }
350+
351+ // Get the source code of the assembly as will be used to check Power FX Namespaces
353352 var code = DecompileModuleToCSharp ( assembly ) ;
354353
355354 foreach ( TypeDefinition type in module . GetAllTypes ( ) )
@@ -365,38 +364,45 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
365364 {
366365 if ( CheckPropertyArrayContainsValue ( type , "Namespaces" , out var values ) )
367366 {
368- foreach ( var name in values )
367+ // For provider types, always allow Preview namespace (preview namespace checks apply to actions only)
368+ var allowedForProvider = settings . AllowPowerFxNamespaces . ToList ( ) ;
369+ if ( ! allowedForProvider . Contains ( NAMESPACE_PREVIEW ) )
369370 {
370- // Check against deny list using regular expressions
371- if ( settings . DenyPowerFxNamespaces . Any ( pattern => Regex . IsMatch ( name , WildcardToRegex ( pattern ) ) ) )
372- {
373- Logger . LogInformation ( $ "Deny Power FX Namespace { name } for { type . Name } ") ;
374- return false ;
375- }
376-
377- // Check against deny wildcard and allow list using regular expressions
378- if ( settings . DenyPowerFxNamespaces . Any ( pattern => pattern == "*" ) &&
379- ( ! settings . AllowPowerFxNamespaces . Any ( pattern => Regex . IsMatch ( name , WildcardToRegex ( pattern ) ) ) &&
371+ allowedForProvider . Add ( NAMESPACE_PREVIEW ) ;
372+ }
373+
374+ foreach ( var name in values )
375+ {
376+ // Check against deny list using regular expressions
377+ if ( settings . DenyPowerFxNamespaces . Any ( pattern => Regex . IsMatch ( name , WildcardToRegex ( pattern ) ) ) )
378+ {
379+ Logger . LogInformation ( $ "Deny Power FX Namespace { name } for { type . Name } ") ;
380+ return false ;
381+ }
382+
383+ // Check against deny wildcard and allow list using regular expressions
384+ if ( settings . DenyPowerFxNamespaces . Any ( pattern => pattern == "*" ) &&
385+ ( ! allowedForProvider . Any ( pattern => Regex . IsMatch ( name , WildcardToRegex ( pattern ) ) ) &&
380386 name != NAMESPACE_TEST_ENGINE ) )
381- {
382- Logger . LogInformation ( $ "Deny Power FX Namespace { name } for { type . Name } ") ;
383- return false ;
384- }
387+ {
388+ Logger . LogInformation ( $ "Deny Power FX Namespace { name } for { type . Name } ") ;
389+ return false ;
390+ }
385391
386- // Check against allow list using regular expressions
387- if ( ! settings . AllowPowerFxNamespaces . Any ( pattern => Regex . IsMatch ( name , WildcardToRegex ( pattern ) ) ) &&
392+ // Check against allow list using regular expressions
393+ if ( ! allowedForProvider . Any ( pattern => Regex . IsMatch ( name , WildcardToRegex ( pattern ) ) ) &&
388394 name != NAMESPACE_TEST_ENGINE )
389- {
390- Logger . LogInformation ( $ "Not allow Power FX Namespace { name } for { type . Name } ") ;
391- return false ;
392- }
393- }
394- }
395- }
396-
397- // Extension Module Check are based on constructor
398- if ( type . BaseType != null && type . BaseType . Name == "ReflectionFunction" )
399- {
395+ {
396+ Logger . LogInformation ( $ "Not allow Power FX Namespace { name } for { type . Name } ") ;
397+ return false ;
398+ }
399+ }
400+ }
401+ }
402+
403+ // Extension Module Check are based on constructor
404+ if ( type . BaseType != null && type . BaseType . Name == "ReflectionFunction" )
405+ {
400406 // Special handling for PauseFunction - allow root namespace when PauseModule is present
401407 if ( type . Name == "PauseFunction" && pauseModule != null )
402408 {
@@ -461,6 +467,13 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
461467 return false ;
462468 }
463469
470+ // For functions defined in assemblies that are providers, allow Preview namespace
471+ var allowedForFunction = settings . AllowPowerFxNamespaces . ToList ( ) ;
472+ if ( assemblyHasProvider && ! allowedForFunction . Contains ( NAMESPACE_PREVIEW ) )
473+ {
474+ allowedForFunction . Add ( NAMESPACE_PREVIEW ) ;
475+ }
476+
464477 if ( settings . DenyPowerFxNamespaces . Contains ( name ) )
465478 {
466479 // Deny list match
@@ -469,8 +482,8 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
469482 }
470483
471484 if ( ( settings . DenyPowerFxNamespaces . Contains ( "*" ) && (
472- ! settings . AllowPowerFxNamespaces . Contains ( name ) ||
473- ( ! settings . AllowPowerFxNamespaces . Contains ( name ) && name != NAMESPACE_TEST_ENGINE )
485+ ! allowedForFunction . Contains ( name ) ||
486+ ( ! allowedForFunction . Contains ( name ) && name != NAMESPACE_TEST_ENGINE )
474487 )
475488 ) )
476489 {
@@ -479,13 +492,104 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s
479492 return false ;
480493 }
481494
482- if ( ! settings . AllowPowerFxNamespaces . Contains ( name ) && name != NAMESPACE_TEST_ENGINE )
495+ if ( ! allowedForFunction . Contains ( name ) && name != NAMESPACE_TEST_ENGINE )
483496 {
484497 Logger . LogInformation ( $ "Do not allow Power FX Namespace { name } for { type . Name } ") ;
485498 // Not in allow list or the Reserved TestEngine namespace
486499 return false ;
487500 }
488- }
501+ }
502+
503+ // Special validation for ReflectionAction types. Actions must not declare the Preview namespace
504+ if ( type . BaseType != null && type . BaseType . Name == "ReflectionAction" )
505+ {
506+ // If PauseModule is present, allow certain actions to be declared in the root namespace (skip namespace validation)
507+ if ( pauseModule != null )
508+ {
509+ // Check configured allow-list of action class names/wildcards
510+ var allowActions = settings . AllowActionsInRoot ?? new HashSet < string > ( ) ;
511+ var isAllowedAction = allowActions . Any ( pattern => Regex . IsMatch ( type . Name , WildcardToRegex ( pattern ) ) ) ;
512+ if ( isAllowedAction )
513+ {
514+ Logger ? . LogInformation ( $ "Allowing action { type . Name } in root namespace due to PauseModule presence and AllowActionsInRoot setting.") ;
515+ continue ; // Skip namespace validation for this action
516+ }
517+ }
518+
519+ var constructors = type . GetConstructors ( ) ;
520+
521+ if ( constructors . Count ( ) == 0 )
522+ {
523+ Logger . LogInformation ( $ "No constructor defined for { type . Name } . Found { constructors . Count ( ) } expected 1 or more") ;
524+ return false ;
525+ }
526+
527+ var constructor = constructors . Where ( c => c . HasBody ) . FirstOrDefault ( ) ;
528+
529+ if ( constructor == null || ! constructor . HasBody )
530+ {
531+ Logger . LogInformation ( $ "No constructor body defined for { type . Name } ") ;
532+ return false ;
533+ }
534+
535+ var baseCall = constructor . Body . Instructions ? . FirstOrDefault ( i => i . OpCode == OpCodes . Call && i . Operand is MethodReference && ( ( MethodReference ) i . Operand ) . Name == ".ctor" ) ;
536+ if ( baseCall == null )
537+ {
538+ Logger . LogInformation ( $ "No base constructor defined for { type . Name } ") ;
539+ return false ;
540+ }
541+
542+ MethodReference baseConstructor = ( MethodReference ) baseCall . Operand ;
543+ if ( baseConstructor . Parameters ? . Count ( ) < 2 )
544+ {
545+ Logger . LogInformation ( $ "No not enough parameters for { type . Name } ") ;
546+ return false ;
547+ }
548+
549+ if ( baseConstructor . Parameters [ 0 ] . ParameterType . FullName != "Microsoft.PowerFx.Core.Utils.DPath" )
550+ {
551+ Logger . LogInformation ( $ "No Power FX Namespace for { type . Name } ") ;
552+ return false ;
553+ }
554+
555+ // Extract namespace from decompiled source
556+ var actionName = GetPowerFxNamespace ( type . Name , code ) ;
557+ if ( string . IsNullOrEmpty ( actionName ) )
558+ {
559+ Logger . LogInformation ( $ "No Power FX Namespace found for { type . Name } ") ;
560+ return false ;
561+ }
562+
563+ // Actions must not use the Preview namespace
564+ if ( string . Equals ( actionName , NAMESPACE_PREVIEW , StringComparison . OrdinalIgnoreCase ) )
565+ {
566+ Logger . LogInformation ( $ "Deny Preview Power FX Namespace { actionName } for action { type . Name } ") ;
567+ return false ;
568+ }
569+
570+ // Continue with the same allow/deny validation as functions
571+ if ( settings . DenyPowerFxNamespaces . Contains ( actionName ) )
572+ {
573+ Logger . LogInformation ( $ "Deny Power FX Namespace { actionName } for { type . Name } ") ;
574+ return false ;
575+ }
576+
577+ if ( ( settings . DenyPowerFxNamespaces . Contains ( "*" ) && (
578+ ! settings . AllowPowerFxNamespaces . Contains ( actionName ) ||
579+ ( ! settings . AllowPowerFxNamespaces . Contains ( actionName ) && actionName != NAMESPACE_TEST_ENGINE )
580+ )
581+ ) )
582+ {
583+ Logger . LogInformation ( $ "Deny Power FX Namespace { actionName } for { type . Name } ") ;
584+ return false ;
585+ }
586+
587+ if ( ! settings . AllowPowerFxNamespaces . Contains ( actionName ) && actionName != NAMESPACE_TEST_ENGINE )
588+ {
589+ Logger . LogInformation ( $ "Do not allow Power FX Namespace { actionName } for { type . Name } ") ;
590+ return false ;
591+ }
592+ }
489593 }
490594 }
491595 return isValid ;
0 commit comments