azdev 0.1.90__tar.gz → 0.1.93__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {azdev-0.1.90 → azdev-0.1.93}/HISTORY.rst +13 -0
  2. {azdev-0.1.90/azdev.egg-info → azdev-0.1.93}/PKG-INFO +14 -1
  3. {azdev-0.1.90 → azdev-0.1.93}/azdev/__init__.py +1 -1
  4. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/breaking_change/__init__.py +18 -6
  5. azdev-0.1.93/azdev/operations/breaking_change/markdown_template.jinja2 +37 -0
  6. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/constant.py +6 -1
  7. azdev-0.1.93/azdev/operations/linter/data/cmd_example_config.json +10 -0
  8. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/linter.py +77 -2
  9. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/command_coverage_rules.py +8 -0
  10. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/command_group_rules.py +5 -3
  11. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/command_rules.py +5 -3
  12. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/parameter_rules.py +5 -3
  13. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/util.py +27 -1
  14. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/regex.py +28 -0
  15. {azdev-0.1.90 → azdev-0.1.93/azdev.egg-info}/PKG-INFO +14 -1
  16. {azdev-0.1.90 → azdev-0.1.93}/azdev.egg-info/SOURCES.txt +1 -0
  17. {azdev-0.1.90 → azdev-0.1.93}/setup.py +1 -0
  18. azdev-0.1.90/azdev/operations/breaking_change/markdown_template.jinja2 +0 -28
  19. {azdev-0.1.90 → azdev-0.1.93}/LICENSE +0 -0
  20. {azdev-0.1.90 → azdev-0.1.93}/MANIFEST.in +0 -0
  21. {azdev-0.1.90 → azdev-0.1.93}/README.md +0 -0
  22. {azdev-0.1.90 → azdev-0.1.93}/README.rst +0 -0
  23. {azdev-0.1.90 → azdev-0.1.93}/azdev/__main__.py +0 -0
  24. {azdev-0.1.90 → azdev-0.1.93}/azdev/commands.py +0 -0
  25. {azdev-0.1.90 → azdev-0.1.93}/azdev/completer.py +0 -0
  26. {azdev-0.1.90 → azdev-0.1.93}/azdev/config/__init__.py +0 -0
  27. {azdev-0.1.90 → azdev-0.1.93}/azdev/config/cli.flake8 +0 -0
  28. {azdev-0.1.90 → azdev-0.1.93}/azdev/config/cli_pylintrc +0 -0
  29. {azdev-0.1.90 → azdev-0.1.93}/azdev/config/ext.flake8 +0 -0
  30. {azdev-0.1.90 → azdev-0.1.93}/azdev/config/ext_pylintrc +0 -0
  31. {azdev-0.1.90 → azdev-0.1.93}/azdev/help.py +0 -0
  32. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/HISTORY.rst +0 -0
  33. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/README.rst +0 -0
  34. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/_client_factory.py +0 -0
  35. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/_help.py +0 -0
  36. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/_params.py +0 -0
  37. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/_validators.py +0 -0
  38. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/azext_metadata.json +0 -0
  39. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/blank__init__.py +0 -0
  40. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/commands.py +0 -0
  41. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/custom.py +0 -0
  42. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/module__init__.py +0 -0
  43. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/pkg_declare__init__.py +0 -0
  44. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/setup.cfg +0 -0
  45. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/setup.py +0 -0
  46. {azdev-0.1.90 → azdev-0.1.93}/azdev/mod_templates/test_service_scenario.py +0 -0
  47. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/__init__.py +0 -0
  48. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/__init__.py +0 -0
  49. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/_macros.j2 +0 -0
  50. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/cmdcov.py +0 -0
  51. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/component.css +0 -0
  52. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/component.js +0 -0
  53. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/favicon.ico +0 -0
  54. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/index.j2 +0 -0
  55. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/index2.j2 +0 -0
  56. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/cmdcov/module.j2 +0 -0
  57. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/code_gen.py +0 -0
  58. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/command_change/__init__.py +0 -0
  59. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/command_change/custom.py +0 -0
  60. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/command_change/util.py +0 -0
  61. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/extensions/__init__.py +0 -0
  62. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/extensions/util.py +0 -0
  63. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/extensions/version_upgrade.py +0 -0
  64. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/help/__init__.py +0 -0
  65. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/help/refdoc/__init__.py +0 -0
  66. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/help/refdoc/conf.py +0 -0
  67. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/legal.py +0 -0
  68. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/__init__.py +0 -0
  69. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/pylint_checkers/__init__.py +0 -0
  70. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/pylint_checkers/show_command.py +0 -0
  71. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rule_decorators.py +0 -0
  72. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/__init__.py +0 -0
  73. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/ci_exclusions.yml +0 -0
  74. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/help_rules.py +0 -0
  75. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/linter/rules/linter_exclusions.yml +0 -0
  76. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/performance.py +0 -0
  77. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/pypi.py +0 -0
  78. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/python_sdk.py +0 -0
  79. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/resource.py +0 -0
  80. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/secret.py +0 -0
  81. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/setup.py +0 -0
  82. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/statistics/__init__.py +0 -0
  83. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/statistics/util.py +0 -0
  84. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/style.py +0 -0
  85. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/testtool/__init__.py +0 -0
  86. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/testtool/incremental_strategy.py +0 -0
  87. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/testtool/profile_context.py +0 -0
  88. {azdev-0.1.90 → azdev-0.1.93}/azdev/operations/testtool/pytest_runner.py +0 -0
  89. {azdev-0.1.90 → azdev-0.1.93}/azdev/params.py +0 -0
  90. {azdev-0.1.90 → azdev-0.1.93}/azdev/transformers.py +0 -0
  91. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/__init__.py +0 -0
  92. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/command.py +0 -0
  93. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/config.py +0 -0
  94. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/const.py +0 -0
  95. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/display.py +0 -0
  96. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/git_util.py +0 -0
  97. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/path.py +0 -0
  98. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/pypi.py +0 -0
  99. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/testing.py +0 -0
  100. {azdev-0.1.90 → azdev-0.1.93}/azdev/utilities/tools.py +0 -0
  101. {azdev-0.1.90 → azdev-0.1.93}/azdev.egg-info/dependency_links.txt +0 -0
  102. {azdev-0.1.90 → azdev-0.1.93}/azdev.egg-info/entry_points.txt +0 -0
  103. {azdev-0.1.90 → azdev-0.1.93}/azdev.egg-info/requires.txt +0 -0
  104. {azdev-0.1.90 → azdev-0.1.93}/azdev.egg-info/top_level.txt +0 -0
  105. {azdev-0.1.90 → azdev-0.1.93}/pyproject.toml +0 -0
  106. {azdev-0.1.90 → azdev-0.1.93}/setup.cfg +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  Release History
4
4
  ===============
5
+ 0.1.93
6
+ ++++++
7
+ * `azdev linter`: Fix `None` path for added files in `git diff` for `missing_command_example` rule
8
+
9
+ 0.1.92
10
+ ++++++
11
+ * `azdev linter`: Add `missing_command_example` rule
12
+ * `azdev linter`: Set `disallowed_html_tags` and `broken_site_link` detection in `high` severity to block future format issues
13
+
14
+ 0.1.91
15
+ ++++++
16
+ * `azdev generate-breaking-change-report`: Update report report template.
17
+
5
18
  0.1.90
6
19
  ++++++
7
20
  * `azdev cmdcov`: Fix incorrect detection of code changes as new commands
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azdev
3
- Version: 0.1.90
3
+ Version: 0.1.93
4
4
  Summary: Microsoft Azure CLI Developer Tools
5
5
  Home-page: https://github.com/Azure/azure-cli-dev-tools
6
6
  Author: Microsoft Corporation
@@ -148,6 +148,19 @@ License
148
148
 
149
149
  Release History
150
150
  ===============
151
+ 0.1.93
152
+ ++++++
153
+ * `azdev linter`: Fix `None` path for added files in `git diff` for `missing_command_example` rule
154
+
155
+ 0.1.92
156
+ ++++++
157
+ * `azdev linter`: Add `missing_command_example` rule
158
+ * `azdev linter`: Set `disallowed_html_tags` and `broken_site_link` detection in `high` severity to block future format issues
159
+
160
+ 0.1.91
161
+ ++++++
162
+ * `azdev generate-breaking-change-report`: Update report report template.
163
+
151
164
  0.1.90
152
165
  ++++++
153
166
  * `azdev cmdcov`: Fix incorrect detection of code changes as new commands
@@ -4,4 +4,4 @@
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __VERSION__ = '0.1.90'
7
+ __VERSION__ = '0.1.93'
@@ -25,6 +25,15 @@ class BreakingChangeItem:
25
25
  self.command = command
26
26
  self.detail = detail
27
27
  self.target_version = target_version
28
+ self.group_ref = None
29
+
30
+ def calc_ref(self, loader):
31
+ if not loader:
32
+ return
33
+ if self.command in loader.command_group_table:
34
+ self.group_ref = self.command.split()
35
+ else:
36
+ self.group_ref = self.command.split()[:-1]
28
37
 
29
38
 
30
39
  def _load_commands():
@@ -247,7 +256,9 @@ def _handle_upcoming_breaking_changes(selected_mod_names, source):
247
256
  yield from _handle_core(source)
248
257
 
249
258
  for module, loader in _iter_and_prepare_module_loader(command_loader, selected_mod_names):
250
- yield from _handle_module(module, loader, source)
259
+ for bc_item in _handle_module(module, loader, source):
260
+ bc_item.calc_ref(loader)
261
+ yield bc_item
251
262
 
252
263
 
253
264
  def _filter_breaking_changes(iterator, max_version=None):
@@ -276,18 +287,19 @@ def _group_breaking_change_items(iterator, group_by_version=False):
276
287
  if group_by_version:
277
288
  upcoming_breaking_changes = defaultdict( # module to command
278
289
  lambda: defaultdict( # command to version
279
- lambda: defaultdict( # version to list of breaking changes
280
- lambda: [])))
290
+ lambda: {'group_ref': None, 'items': defaultdict( # version to list of breaking changes
291
+ lambda: [])}))
281
292
  else:
282
293
  upcoming_breaking_changes = defaultdict( # module to command
283
294
  lambda: defaultdict( # command to list of breaking changes
284
- lambda: []))
295
+ lambda: {'group_ref': None, 'items': []}))
285
296
  for item in iterator:
286
297
  version = item.target_version if item.target_version else 'Unspecific'
298
+ upcoming_breaking_changes[item.module][item.command]['group_ref'] = item.group_ref
287
299
  if group_by_version:
288
- upcoming_breaking_changes[item.module][item.command][version].append(item.detail)
300
+ upcoming_breaking_changes[item.module][item.command]['items'][version].append(item.detail)
289
301
  else:
290
- upcoming_breaking_changes[item.module][item.command].append(item.detail)
302
+ upcoming_breaking_changes[item.module][item.command]['items'].append(item.detail)
291
303
  return upcoming_breaking_changes
292
304
 
293
305
 
@@ -0,0 +1,37 @@
1
+ # Upcoming breaking changes in Azure CLI
2
+
3
+ The breaking changes listed in this article are planned for the next major release of the Azure CLI unless otherwise noted. Per our [Support lifecycle](./azure-cli-support-lifecycle.md), breaking changes in Azure CLI Core reference groups occur twice a year.
4
+
5
+ {% for module, command_bc in module_bc.items() -%}
6
+ ## {{ module }}
7
+
8
+ {% for command, multi_version_bcs in command_bc.items() -%}
9
+ {% if not (module == 'core' and command == 'core') -%}
10
+ ### `{{ command }}`
11
+
12
+ {% endif -%}
13
+ {% if multi_version_bcs.group_ref -%}
14
+ [Link to {{ multi_version_bcs.group_ref|join(' ') }} reference group](/cli/azure/{{ multi_version_bcs.group_ref|join('/') }})
15
+
16
+ {% endif -%}
17
+ {% if multi_version_bcs['items'] is mapping -%}
18
+ {% for version, bcs in multi_version_bcs['items'] | dictsort -%}
19
+ ###{%- if not (module == 'core' and command == 'core') -%}#{%- endif %} Deprecated in {{ version }}
20
+
21
+ {% for bc in bcs -%}
22
+ - {{ bc.detail }}
23
+ {% endfor %}
24
+
25
+ {% endfor -%}
26
+ {% else -%}
27
+
28
+ {% for bc in multi_version_bcs['items'] -%}
29
+ - {{ bc }}
30
+ {% endfor %}
31
+
32
+ {% endif -%}
33
+ {% endfor -%}
34
+ {% endfor -%}
35
+
36
+ > [!NOTE]
37
+ > This article provides information on upcoming breaking changes. For previously published breaking changes, see [Azure CLI release notes](./release-notes-azure-cli.md).
@@ -4,7 +4,7 @@
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
6
  # pylint: disable=line-too-long
7
-
7
+ import os
8
8
  ENCODING = 'utf-8'
9
9
 
10
10
  # Base on https://github.com/Azure/azure-cli/blob/dev/.github/CODEOWNERS
@@ -259,3 +259,8 @@ VERSION_PREVIEW_TAG = "preview"
259
259
  PREVIEW_INIT_SUFFIX = "b1"
260
260
 
261
261
  CLI_EXTENSION_INDEX_URL = "https://azcliextensionsync.blob.core.windows.net/index1/index.json"
262
+
263
+ CMD_EXAMPLE_CONFIG_FILE = "./data/cmd_example_config.json"
264
+ CMD_EXAMPLE_CONFIG_FILE_PATH = f"{os.path.dirname(os.path.realpath(__file__))}/linter/{CMD_EXAMPLE_CONFIG_FILE}"
265
+ CMD_EXAMPLE_CONFIG_FILE_URL = "https://azcmdchangemgmt.blob.core.windows.net/azure-cli-dev-tool-config/cmd_example_config.json"
266
+ CMD_EXAMPLE_DEFAULT = 1
@@ -0,0 +1,10 @@
1
+ {
2
+ "create": 1,
3
+ "add": 1,
4
+ "update": 1,
5
+ "list": 0,
6
+ "delete": 0,
7
+ "remove": 0,
8
+ "show": 0,
9
+ "wait": 0
10
+ }
@@ -3,7 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
-
6
+ # pylint: disable=line-too-long
7
7
  from difflib import context_diff
8
8
  from enum import Enum
9
9
  from importlib import import_module
@@ -17,6 +17,7 @@ from knack.log import get_logger
17
17
 
18
18
  from azdev.operations.regex import (
19
19
  get_all_tested_commands_from_regex,
20
+ search_aaz_raw_command, search_aaz_custom_command,
20
21
  search_argument,
21
22
  search_argument_context,
22
23
  search_command,
@@ -24,7 +25,8 @@ from azdev.operations.regex import (
24
25
  search_command_group)
25
26
  from azdev.utilities import diff_branches_detail, diff_branch_file_patch
26
27
  from 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)
28
30
 
29
31
  PACKAGE_NAME = 'azdev.operations.linter'
30
32
  _logger = get_logger(__name__)
@@ -224,6 +226,31 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
224
226
  all_tested_command = self._detect_tested_command(diff_index)
225
227
  return self._run_parameter_test_coverage(parameters, all_tested_command)
226
228
 
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
+ cmd_suffix = cmd.split()[-1]
241
+ cmd_example_threshold = get_cmd_example_threshold(cmd_suffix, cmd_example_config)
242
+ if cmd_example_threshold == 0:
243
+ continue
244
+ if not hasattr(cmd_help, "parameters") or len(cmd_help.parameters) == 0:
245
+ # skip cmd without parameters
246
+ continue
247
+ if not hasattr(cmd_help, "examples") or len(cmd_help.examples) < cmd_example_threshold:
248
+ violations.append(f'Command `{cmd}` should have at least {cmd_example_threshold} example(s)')
249
+ if violations:
250
+ violations.insert(0, 'Check command example failed.')
251
+ violations.extend(['Please add examples for the modified command or add the command in rule_exclusions: missing_command_example in linter_exclusions.yml'])
252
+ return violations
253
+
227
254
  def _get_exclusions(self):
228
255
  _exclude_commands = set()
229
256
  _exclude_parameters = set()
@@ -238,6 +265,16 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
238
265
  _logger.debug('exclude_comands: %s', _exclude_commands)
239
266
  return _exclude_commands, _exclude_parameters
240
267
 
268
+ def _get_cmd_exclusions(self, rule_name=None):
269
+ _exclude_commands = set()
270
+ if not rule_name:
271
+ return _exclude_commands
272
+ for command, details in self.exclusions.items():
273
+ if 'rule_exclusions' in details and rule_name in details['rule_exclusions']:
274
+ _exclude_commands.add(command)
275
+ _logger.debug('exclude_commands: %s', _exclude_commands)
276
+ return _exclude_commands
277
+
241
278
  def _split_path(self, path: str):
242
279
  parts = path.rsplit('/', maxsplit=1)
243
280
  return parts if len(parts) == 2 else ('', parts[0])
@@ -387,11 +424,49 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
387
424
  'Or add the parameter with missing_parameter_test_coverage rule in linter_exclusions.yml'])
388
425
  return exec_state, violations
389
426
 
427
+ def _detect_modified_command(self):
428
+ modified_commands = set()
429
+ diff_patches = diff_branch_file_patch(repo=self.git_repo, target=self.git_target, source=self.git_source)
430
+ for change in diff_patches:
431
+ if not change.b_path or not change.diff:
432
+ continue
433
+ file_path, filename = self._split_path(change.b_path)
434
+ if "commands.py" not in filename and "aaz" not in file_path:
435
+ continue
436
+ current_lines = self._read_blob_lines(change.b_blob)
437
+ patch = change.diff.decode("utf-8")
438
+ patch_lines = patch.splitlines()
439
+ if 'commands.py' in filename:
440
+ added_lines = [line for line in patch_lines if line.startswith('+') and not line.startswith('+++')]
441
+ for line in added_lines:
442
+ if aaz_custom_command := search_aaz_custom_command(line):
443
+ modified_commands.add(aaz_custom_command)
444
+
445
+ for row_num, line in enumerate(patch_lines):
446
+ if not line.startswith("+") or line.startswith('+++'):
447
+ continue
448
+ manual_command_suffix = search_command(line)
449
+ if manual_command_suffix:
450
+ idx = self._get_line_number(patch_lines, row_num, r'@@ -(\d+),(?:\d+) \+(?:\d+),(?:\d+) @@')
451
+ manual_command = search_command_group(idx, current_lines, manual_command_suffix)
452
+ if manual_command:
453
+ modified_commands.add(manual_command)
454
+
455
+ if "aaz" in file_path:
456
+ if aaz_raw_command := search_aaz_raw_command(patch):
457
+ modified_commands.add(aaz_raw_command)
458
+
459
+ commands = list(modified_commands)
460
+ _logger.debug('Modified commands: %s', modified_commands)
461
+ return commands
462
+
390
463
  def _get_diffed_patches(self):
391
464
  if not self.git_source or not self.git_target or not self.git_repo:
392
465
  return
393
466
  diff_patches = diff_branch_file_patch(repo=self.git_repo, target=self.git_target, source=self.git_source)
394
467
  for change in diff_patches:
468
+ if not change.diff:
469
+ continue
395
470
  patch = change.diff.decode("utf-8")
396
471
  added_lines = [line for line in patch.splitlines() if line.startswith('+') and not line.startswith('+++')]
397
472
  self.diffed_lines |= set(added_lines)
@@ -22,3 +22,11 @@ def missing_parameter_test_coverage(linter):
22
22
  if not exec_state:
23
23
  violation_msg = "\n\t".join(violations)
24
24
  raise RuleError(violation_msg + "\n")
25
+
26
+
27
+ @CommandCoverageRule(LinterSeverity.HIGH)
28
+ def missing_command_example(linter):
29
+ violations = linter.check_missing_command_example()
30
+ if violations:
31
+ violation_msg = "\n\t".join(violations)
32
+ raise RuleError(violation_msg + "\n")
@@ -45,7 +45,7 @@ def require_wait_command_if_no_wait(linter, command_group_name):
45
45
  raise RuleError("Group does not have a 'wait' command, yet '{}' exposes '--no-wait'".format(cmd))
46
46
 
47
47
 
48
- @CommandGroupRule(LinterSeverity.MEDIUM)
48
+ @CommandGroupRule(LinterSeverity.HIGH)
49
49
  def disallowed_html_tag_from_command_group(linter, command_group_name):
50
50
  if command_group_name == '' or not linter.get_loaded_help_entry(command_group_name):
51
51
  return
@@ -69,9 +69,11 @@ def broken_site_link_from_command_group(linter, command_group_name):
69
69
  if command_group_name == '' or not linter.get_loaded_help_entry(command_group_name):
70
70
  return
71
71
  help_entry = linter.get_loaded_help_entry(command_group_name)
72
- if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary)):
72
+ if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary,
73
+ linter.diffed_lines)):
73
74
  raise RuleError("Broken links {} in short summary. "
74
75
  "If link is an example, please wrap it with backtick. ".format(broken_links))
75
- if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary)):
76
+ if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary,
77
+ linter.diffed_lines)):
76
78
  raise RuleError("Broken links {} in long summary. "
77
79
  "If link is an example, please wrap it with backtick. ".format(broken_links))
@@ -40,7 +40,7 @@ def group_delete_commands_should_confirm(linter, command_name):
40
40
  "Please make sure to ask for confirmation.")
41
41
 
42
42
 
43
- @CommandRule(LinterSeverity.MEDIUM)
43
+ @CommandRule(LinterSeverity.HIGH)
44
44
  def disallowed_html_tag_from_command(linter, command_name):
45
45
  if command_name == '' or not linter.get_loaded_help_entry(command_name):
46
46
  return
@@ -64,9 +64,11 @@ def broken_site_link_from_command(linter, command_name):
64
64
  if command_name == '' or not linter.get_loaded_help_entry(command_name):
65
65
  return
66
66
  help_entry = linter.get_loaded_help_entry(command_name)
67
- if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary)):
67
+ if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary,
68
+ linter.diffed_lines)):
68
69
  raise RuleError("Broken links {} in short summary. "
69
70
  "If link is an example, please wrap it with backtick. ".format(broken_links))
70
- if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary)):
71
+ if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary,
72
+ linter.diffed_lines)):
71
73
  raise RuleError("Broken links {} in long summary. "
72
74
  "If link is an example, please wrap it with backtick. ".format(broken_links))
@@ -174,7 +174,7 @@ def option_should_not_contain_under_score(linter, command_name, parameter_name):
174
174
  raise RuleError("Argument's option {} contains '_' which should be '-' instead.".format(option))
175
175
 
176
176
 
177
- @ParameterRule(LinterSeverity.MEDIUM)
177
+ @ParameterRule(LinterSeverity.HIGH)
178
178
  def disallowed_html_tag_from_parameter(linter, command_name, parameter_name):
179
179
  if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name):
180
180
  return
@@ -199,9 +199,11 @@ def broken_site_link_from_parameter(linter, command_name, parameter_name):
199
199
  if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name):
200
200
  return
201
201
  help_entry = linter.get_parameter_help_info(command_name, parameter_name)
202
- if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary)):
202
+ if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary,
203
+ linter.diffed_lines)):
203
204
  raise RuleError("Broken links {} in short summary. "
204
205
  "If link is an example, please wrap it with backtick. ".format(broken_links))
205
- if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary)):
206
+ if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary,
207
+ linter.diffed_lines)):
206
208
  raise RuleError("Broken links {} in long summary. "
207
209
  "If link is an example, please wrap it with backtick. ".format(broken_links))
@@ -6,12 +6,15 @@
6
6
 
7
7
  import copy
8
8
  import re
9
+ import os
10
+ import json
9
11
  import requests
10
12
 
11
13
  from knack.log import get_logger
12
14
 
13
15
  from azdev.utilities import get_name_index
14
- from azdev.operations.constant import ALLOWED_HTML_TAG
16
+ from azdev.operations.constant import (ALLOWED_HTML_TAG, CMD_EXAMPLE_CONFIG_FILE_URL,
17
+ CMD_EXAMPLE_CONFIG_FILE_PATH, CMD_EXAMPLE_DEFAULT)
15
18
 
16
19
 
17
20
  logger = get_logger(__name__)
@@ -155,3 +158,26 @@ def has_broken_site_links(help_message, filtered_lines=None):
155
158
  if filtered_lines:
156
159
  invalid_urls = [s for s in invalid_urls if any(s in diff_line for diff_line in filtered_lines)]
157
160
  return invalid_urls
161
+
162
+
163
+ def get_cmd_example_configurations():
164
+ cmd_example_threshold = {}
165
+ remote_res = requests.get(CMD_EXAMPLE_CONFIG_FILE_URL)
166
+ if remote_res.status_code != 200:
167
+ logger.warning("remote cmd example configuration fetch error, use local dict")
168
+ if not os.path.exists(CMD_EXAMPLE_CONFIG_FILE_PATH):
169
+ logger.info("cmd_example_config.json not exist, skipped")
170
+ return cmd_example_threshold
171
+ with open(CMD_EXAMPLE_CONFIG_FILE_PATH, "r") as f_in:
172
+ cmd_example_threshold = json.load(f_in)
173
+ else:
174
+ logger.info("remote cmd example configuration fetch success")
175
+ cmd_example_threshold = remote_res.json()
176
+ return cmd_example_threshold
177
+
178
+
179
+ def get_cmd_example_threshold(cmd_suffix, cmd_example_config):
180
+ for cmd_type, threshold in cmd_example_config.items():
181
+ if cmd_suffix.find(cmd_type) != -1:
182
+ return threshold
183
+ return CMD_EXAMPLE_DEFAULT
@@ -173,3 +173,31 @@ def search_deleted_command(line):
173
173
  if ref:
174
174
  command = ref[0].split(',')[0].strip("'")
175
175
  return command
176
+
177
+
178
+ def search_aaz_custom_command(line):
179
+ """
180
+ re match pattern
181
+ + self.command_table['monitor autoscale update'] = AutoScaleUpdate(loader=self)
182
+ """
183
+ cmd = ''
184
+ aaz_custom_cmd_pattern = r"\+.*\.command_table\[['\"](.*?)['\"]\]"
185
+ ref = re.findall(aaz_custom_cmd_pattern, line)
186
+ if ref:
187
+ cmd = ref[0].strip()
188
+ return cmd
189
+
190
+
191
+ def search_aaz_raw_command(lines):
192
+ """
193
+ re match pattern
194
+ +@register_command(
195
+ + "monitor autoscale update",
196
+ +)
197
+ """
198
+ cmd = ''
199
+ aaz_raw_cmd_pattern = r"\+@register_command\([\s\S]*?\+.*?['\"](.*?)['\"]"
200
+ ref = re.findall(aaz_raw_cmd_pattern, str(lines))
201
+ if ref:
202
+ cmd = ref[0].strip()
203
+ return cmd
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azdev
3
- Version: 0.1.90
3
+ Version: 0.1.93
4
4
  Summary: Microsoft Azure CLI Developer Tools
5
5
  Home-page: https://github.com/Azure/azure-cli-dev-tools
6
6
  Author: Microsoft Corporation
@@ -148,6 +148,19 @@ License
148
148
 
149
149
  Release History
150
150
  ===============
151
+ 0.1.93
152
+ ++++++
153
+ * `azdev linter`: Fix `None` path for added files in `git diff` for `missing_command_example` rule
154
+
155
+ 0.1.92
156
+ ++++++
157
+ * `azdev linter`: Add `missing_command_example` rule
158
+ * `azdev linter`: Set `disallowed_html_tags` and `broken_site_link` detection in `high` severity to block future format issues
159
+
160
+ 0.1.91
161
+ ++++++
162
+ * `azdev generate-breaking-change-report`: Update report report template.
163
+
151
164
  0.1.90
152
165
  ++++++
153
166
  * `azdev cmdcov`: Fix incorrect detection of code changes as new commands
@@ -75,6 +75,7 @@ azdev/operations/linter/__init__.py
75
75
  azdev/operations/linter/linter.py
76
76
  azdev/operations/linter/rule_decorators.py
77
77
  azdev/operations/linter/util.py
78
+ azdev/operations/linter/data/cmd_example_config.json
78
79
  azdev/operations/linter/pylint_checkers/__init__.py
79
80
  azdev/operations/linter/pylint_checkers/show_command.py
80
81
  azdev/operations/linter/rules/__init__.py
@@ -93,6 +93,7 @@ setup(
93
93
  'azdev.config': ['*.*', 'cli_pylintrc', 'ext_pylintrc'],
94
94
  'azdev.mod_templates': ['*.*'],
95
95
  'azdev.operations.linter.rules': ['ci_exclusions.yml'],
96
+ 'azdev.operations.linter': ["data/*"],
96
97
  'azdev.operations.cmdcov': ['*.*'],
97
98
  'azdev.operations.breaking_change': ['*.*'],
98
99
  },
@@ -1,28 +0,0 @@
1
- # Upcoming breaking changes in Azure CLI
2
-
3
- {% for module, command_bc in module_bc.items() -%}
4
- ## {{ module }}
5
-
6
- {% for command, multi_version_bcs in command_bc.items() -%}
7
- {% if not (module == 'core' and command == 'core') -%}
8
- ### `{{ command }}`
9
-
10
- {% endif -%}
11
- {% if multi_version_bcs is mapping -%}
12
- {% for version, bcs in multi_version_bcs | dictsort -%}
13
- ###{%- if not (module == 'core' and command == 'core') -%}#{%- endif %} Deprecated in {{ version }}
14
-
15
- {% for bc in bcs -%}
16
- - {{ bc }}
17
- {% endfor %}
18
-
19
- {% endfor -%}
20
- {% else -%}
21
-
22
- {% for bc in multi_version_bcs -%}
23
- - {{ bc }}
24
- {% endfor %}
25
-
26
- {% endif -%}
27
- {% endfor -%}
28
- {% endfor -%}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes