33# Licensed under the MIT License. See License.txt in the project root for
44# license information.
55# -----------------------------------------------------------------------------
6-
6+ # pylint: disable=line-too-long
77from difflib import context_diff
88from enum import Enum
99from importlib import import_module
1717
1818from azdev .operations .regex import (
1919 get_all_tested_commands_from_regex ,
20+ search_aaz_raw_command , search_aaz_custom_command ,
2021 search_argument ,
2122 search_argument_context ,
2223 search_command ,
2324 search_deleted_command ,
2425 search_command_group )
2526from azdev .utilities import diff_branches_detail , diff_branch_file_patch
2627from azdev .utilities .path import get_cli_repo_path , get_ext_repo_paths
27- from .util import share_element , exclude_commands , LinterError
28+ from .util import (share_element , exclude_commands , LinterError , get_cmd_example_configurations ,
29+ get_cmd_example_threshold )
2830
2931PACKAGE_NAME = 'azdev.operations.linter'
3032_logger = get_logger (__name__ )
@@ -224,6 +226,30 @@ def get_parameter_test_coverage(self):
224226 all_tested_command = self ._detect_tested_command (diff_index )
225227 return self ._run_parameter_test_coverage (parameters , all_tested_command )
226228
229+ def check_missing_command_example (self ):
230+ _exclude_commands = self ._get_cmd_exclusions (rule_name = "missing_command_example" )
231+ cmd_example_config = get_cmd_example_configurations ()
232+ commands = self ._detect_modified_command ()
233+ violations = []
234+ for cmd in commands :
235+ if cmd in _exclude_commands :
236+ continue
237+ cmd_help = self ._loaded_help .get (cmd , None )
238+ if not cmd_help :
239+ continue
240+ # parameters = cmd_help.parameters
241+ # add if future parameter set required
242+ cmd_suffix = cmd .split ()[- 1 ]
243+ cmd_example_threshold = get_cmd_example_threshold (cmd_suffix , cmd_example_config )
244+ if cmd_example_threshold == 0 :
245+ continue
246+ if not hasattr (cmd_help , "examples" ) or len (cmd_help .examples ) < cmd_example_threshold :
247+ violations .append (f'Command `{ cmd } ` should have at least { cmd_example_threshold } example(s)' )
248+ if violations :
249+ violations .insert (0 , 'Check command example failed.' )
250+ violations .extend (['Please add examples for the modified command or add the command in rule_exclusions: missing_command_example in linter_exclusions.yml' ])
251+ return violations
252+
227253 def _get_exclusions (self ):
228254 _exclude_commands = set ()
229255 _exclude_parameters = set ()
@@ -238,6 +264,16 @@ def _get_exclusions(self):
238264 _logger .debug ('exclude_comands: %s' , _exclude_commands )
239265 return _exclude_commands , _exclude_parameters
240266
267+ def _get_cmd_exclusions (self , rule_name = None ):
268+ _exclude_commands = set ()
269+ if not rule_name :
270+ return _exclude_commands
271+ for command , details in self .exclusions .items ():
272+ if 'rule_exclusions' in details and rule_name in details ['rule_exclusions' ]:
273+ _exclude_commands .add (command )
274+ _logger .debug ('exclude_commands: %s' , _exclude_commands )
275+ return _exclude_commands
276+
241277 def _split_path (self , path : str ):
242278 parts = path .rsplit ('/' , maxsplit = 1 )
243279 return parts if len (parts ) == 2 else ('' , parts [0 ])
@@ -387,6 +423,40 @@ def _run_parameter_test_coverage(parameters, all_tested_command):
387423 'Or add the parameter with missing_parameter_test_coverage rule in linter_exclusions.yml' ])
388424 return exec_state , violations
389425
426+ def _detect_modified_command (self ):
427+ modified_commands = set ()
428+ diff_patches = diff_branch_file_patch (repo = self .git_repo , target = self .git_target , source = self .git_source )
429+ for change in diff_patches :
430+ file_path , filename = self ._split_path (change .a_path )
431+ if "commands.py" not in filename and "aaz" not in file_path :
432+ continue
433+ current_lines = self ._read_blob_lines (change .b_blob )
434+ patch = change .diff .decode ("utf-8" )
435+ patch_lines = patch .splitlines ()
436+ if 'commands.py' in filename :
437+ added_lines = [line for line in patch_lines if line .startswith ('+' ) and not line .startswith ('+++' )]
438+ for line in added_lines :
439+ if aaz_custom_command := search_aaz_custom_command (line ):
440+ modified_commands .add (aaz_custom_command )
441+
442+ for row_num , line in enumerate (patch_lines ):
443+ if not line .startswith ("+" ) or line .startswith ('+++' ):
444+ continue
445+ manual_command_suffix = search_command (line )
446+ if manual_command_suffix :
447+ idx = self ._get_line_number (patch_lines , row_num , r'@@ -(\d+),(?:\d+) \+(?:\d+),(?:\d+) @@' )
448+ manual_command = search_command_group (idx , current_lines , manual_command_suffix )
449+ if manual_command :
450+ modified_commands .add (manual_command )
451+
452+ if "aaz" in file_path :
453+ if aaz_raw_command := search_aaz_raw_command (patch ):
454+ modified_commands .add (aaz_raw_command )
455+
456+ commands = list (modified_commands )
457+ _logger .debug ('Modified commands: %s' , modified_commands )
458+ return commands
459+
390460 def _get_diffed_patches (self ):
391461 if not self .git_source or not self .git_target or not self .git_repo :
392462 return
0 commit comments