1212// limitations under the License.
1313// ----------------------------------------------------------------------------------
1414
15- //#define SERIALIZE
16-
1715using Newtonsoft . Json ;
1816
1917using NJsonSchema ;
2321using System . Collections . Generic ;
2422using System . IO ;
2523using System . Linq ;
26- using System . Management . Automation ;
2724using System . Reflection ;
25+ using System . Text . RegularExpressions ;
26+
2827using Tools . Common . Issues ;
2928using Tools . Common . Loaders ;
3029using Tools . Common . Loggers ;
3130using Tools . Common . Models ;
3231using Tools . Common . Utilities ;
3332
33+ using ParameterMetadata = Tools . Common . Models . ParameterMetadata ;
34+ using ParameterSetMetadata = Tools . Common . Models . ParameterSetMetadata ;
35+
3436namespace StaticAnalysis . UXMetadataAnalyzer
3537{
3638 public class UXMetadataAnalyzer : IStaticAnalyzer
@@ -75,7 +77,7 @@ public void Analyze(
7577 Func < string , bool > cmdletFilter ,
7678 IEnumerable < string > modulesToAnalyze )
7779 {
78- var processedHelpFiles = new List < string > ( ) ;
80+ var savedDirectory = Directory . GetCurrentDirectory ( ) ;
7981 var issueLogger = Logger . CreateLogger < UXMetadataIssue > ( "UXMetadataIssues.csv" ) ;
8082
8183 if ( directoryFilter != null )
@@ -102,6 +104,10 @@ public void Analyze(
102104 }
103105 string moduleName = Path . GetFileName ( directory ) ;
104106
107+ Directory . SetCurrentDirectory ( directory ) ;
108+
109+ var moduleMetadata = MetadataLoader . GetModuleMetadata ( moduleName ) ;
110+
105111 string UXFolder = Path . Combine ( directory , "UX" ) ;
106112 if ( ! Directory . Exists ( UXFolder ) )
107113 {
@@ -111,24 +117,154 @@ public void Analyze(
111117 var UXMetadataPathList = Directory . EnumerateFiles ( UXFolder , "*.json" , SearchOption . AllDirectories ) ;
112118 foreach ( var UXMetadataPath in UXMetadataPathList )
113119 {
114- ValidateUXMetadata ( moduleName , UXMetadataPath , issueLogger ) ;
120+ ValidateUXMetadata ( moduleName , UXMetadataPath , moduleMetadata , issueLogger ) ;
115121 }
122+ Directory . SetCurrentDirectory ( savedDirectory ) ;
116123 }
117124 }
118125 }
119126
120- private void ValidateUXMetadata ( string moduleName , string UXMatadataPath , ReportLogger < UXMetadataIssue > issueLogger )
127+ private void ValidateSchema ( string moduleName , string resourceType , string subResourceType , string UXMetadataContent , ReportLogger < UXMetadataIssue > issueLogger )
121128 {
122- string data = File . ReadAllText ( UXMatadataPath ) ;
123- var result = schemaValidator . Validate ( data , schema ) ;
124- string resourceType = Path . GetFileName ( Path . GetDirectoryName ( UXMatadataPath ) ) ;
129+ var result = schemaValidator . Validate ( UXMetadataContent , schema ) ;
125130 if ( result != null && result . Count != 0 )
126131 {
127132 foreach ( ValidationError error in result )
128133 {
129- issueLogger . LogUXMetadataIssue ( moduleName , resourceType , UXMatadataPath , 1 , error . ToString ( ) . Replace ( "\n " , "\\ n" ) ) ;
134+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , null , 1 , error . ToString ( ) . Replace ( "\n " , "\\ n" ) ) ;
135+ }
136+ }
137+ }
138+
139+ private void ValidateMetadata ( string moduleName , string resourceType , string subResourceType , string UXMetadataContent , ModuleMetadata moduleMetadata , ReportLogger < UXMetadataIssue > issueLogger )
140+ {
141+ UXMetadata UXMetadata = JsonConvert . DeserializeObject < UXMetadata > ( UXMetadataContent ) ;
142+
143+ foreach ( UXMetadataCommand command in UXMetadata . Commands )
144+ {
145+ string expectLearnUrl = string . Format ( "https://learn.microsoft.com/powershell/module/{0}/{1}" , moduleName , command . Name ) . ToLower ( ) ;
146+
147+ if ( ! expectLearnUrl . Equals ( command . Help . LearnMore . Url , StringComparison . OrdinalIgnoreCase ) )
148+ {
149+ string description = string . Format ( "Doc url is expect: {0} but get: {1}" , expectLearnUrl , command . Help . LearnMore . Url ) ;
150+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , command . Name , 1 , description ) ;
151+ }
152+ if ( command . Path . IndexOf ( resourceType , StringComparison . CurrentCultureIgnoreCase ) == - 1 )
153+ {
154+ string description = string . Format ( "The path {0} doesn't contains the right resource tpye: {1}" , command . Path , resourceType ) ;
155+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , command . Name , 2 , description ) ;
156+ }
157+
158+ CmdletMetadata cmdletMetadata = moduleMetadata . Cmdlets . Find ( x => x . Name == command . Name ) ;
159+ if ( cmdletMetadata == null )
160+ {
161+ string description = string . Format ( "Cmdlet {0} is not contained in {1}." , command . Name , moduleName ) ;
162+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , command . Name , 1 , description ) ;
163+ }
164+
165+ foreach ( UXMetadataCommandExample example in command . Examples )
166+ {
167+ ValidateExample ( moduleName , resourceType , subResourceType , command . Name , cmdletMetadata , example , issueLogger ) ;
168+ }
169+ }
170+ }
171+
172+ private void ValidateExample ( string moduleName , string resourceType , string subResourceType , string commandName , CmdletMetadata cmdletMetadata , UXMetadataCommandExample example , ReportLogger < UXMetadataIssue > issueLogger )
173+ {
174+ List < string > parameterListConvertedFromAlias = example . Parameters . Select ( x =>
175+ {
176+ string parameterNameInExample = x . Name . Trim ( '-' ) ;
177+ foreach ( ParameterMetadata parameterMetadata in cmdletMetadata . Parameters )
178+ {
179+ if ( parameterMetadata . Name . Equals ( parameterNameInExample , StringComparison . CurrentCultureIgnoreCase ) )
180+ {
181+ return parameterMetadata . Name ;
182+ }
183+ foreach ( string alias in parameterMetadata . AliasList )
184+ {
185+ if ( alias . Equals ( parameterNameInExample , StringComparison . CurrentCultureIgnoreCase ) )
186+ {
187+ return parameterMetadata . Name ;
188+ }
189+ }
190+ }
191+ string description = string . Format ( "Cannot find the defination of parameter {0} in example" , parameterNameInExample ) ;
192+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , commandName , 1 , description ) ;
193+ return null ;
194+ } ) . ToList ( ) ;
195+
196+ HashSet < string > parametersInExample = new HashSet < string > ( parameterListConvertedFromAlias . Where ( x => x != null ) ) ;
197+ foreach ( string parameter in parametersInExample )
198+ {
199+ if ( parameterListConvertedFromAlias . Count ( x => parameter . Equals ( x ) ) != 1 )
200+ {
201+ string description = string . Format ( "Multiply reference of parameter {0} in example" , parameter ) ;
202+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , commandName , 1 , description ) ;
130203 }
131204 }
205+ if ( parameterListConvertedFromAlias . Contains ( null ) )
206+ {
207+ return ;
208+ }
209+
210+ bool findMatchedParameterSet = false ;
211+ foreach ( ParameterSetMetadata parameterSetMetadata in cmdletMetadata . ParameterSets )
212+ {
213+ if ( IsExampleMatchParameterSet ( parametersInExample , parameterSetMetadata ) )
214+ {
215+ findMatchedParameterSet = true ;
216+ }
217+ }
218+
219+ if ( ! findMatchedParameterSet )
220+ {
221+ string description = string . Format ( "Cannot find a matched parameter set for example of {0}" , commandName ) ;
222+ issueLogger . LogUXMetadataIssue ( moduleName , resourceType , subResourceType , commandName , 1 , description ) ;
223+ }
224+ }
225+
226+ private bool IsExampleMatchParameterSet ( HashSet < string > parametersInExample , ParameterSetMetadata parameterSetMetadata )
227+ {
228+ List < Parameter > mandatoryParameters = parameterSetMetadata . Parameters . Where ( x => x . Mandatory ) . ToList ( ) ;
229+ foreach ( Parameter parameter in mandatoryParameters )
230+ {
231+ if ( ! parametersInExample . Contains ( parameter . ParameterMetadata . Name ) )
232+ {
233+ return false ;
234+ }
235+ }
236+
237+ foreach ( string parameterName in parametersInExample )
238+ {
239+ if ( ! IsParameterContainedInParameterSet ( parameterName , parameterSetMetadata ) )
240+ {
241+ return false ;
242+ }
243+ }
244+
245+ return true ;
246+ }
247+
248+ private bool IsParameterContainedInParameterSet ( string paramenterName , ParameterSetMetadata parameterSetMetadata )
249+ {
250+ foreach ( Parameter parameterInfo in parameterSetMetadata . Parameters )
251+ {
252+ if ( parameterInfo . ParameterMetadata . Name . Equals ( paramenterName , StringComparison . CurrentCultureIgnoreCase ) )
253+ {
254+ return true ;
255+ }
256+ }
257+
258+ return false ;
259+ }
260+
261+ private void ValidateUXMetadata ( string moduleName , string UXMetadataPath , ModuleMetadata moduleMetadata , ReportLogger < UXMetadataIssue > issueLogger )
262+ {
263+ string UXMetadataContent = File . ReadAllText ( UXMetadataPath ) ;
264+ string resourceType = Path . GetFileName ( Path . GetDirectoryName ( UXMetadataPath ) ) ;
265+ string subResourceType = Path . GetFileName ( UXMetadataPath ) . Replace ( ".json" , "" ) ;
266+ ValidateSchema ( moduleName , resourceType , subResourceType , UXMetadataContent , issueLogger ) ;
267+ ValidateMetadata ( moduleName , resourceType , subResourceType , UXMetadataContent , moduleMetadata , issueLogger ) ;
132268 }
133269
134270
@@ -150,14 +286,15 @@ public AnalysisReport GetAnalysisReport()
150286 public static class LogExtensions
151287 {
152288 public static void LogUXMetadataIssue (
153- this ReportLogger < UXMetadataIssue > issueLogger , string module , string resourceType , string filePath ,
289+ this ReportLogger < UXMetadataIssue > issueLogger , string module , string resourceType , string subResourceType , string command ,
154290 int severity , string description )
155291 {
156292 issueLogger . LogRecord ( new UXMetadataIssue
157293 {
158294 Module = module ,
159295 ResourceType = resourceType ,
160- FilePath = filePath ,
296+ SubResourceType = subResourceType ,
297+ Command = command ,
161298 Description = description ,
162299 Severity = severity ,
163300 } ) ;
0 commit comments