Skip to content

[Test only] #2

Open
Open

Description

@msyyc
id swagger linter tsp lib / migration group tsp linter PR PR created time PR merged time approved by new comment after latest commit Status notes
1 AdditionalPropertiesAndProperties @azure-tools/typespec-azure-core/bad-record-type - - - - - Done -
2 AllProxyResourcesShouldHaveDelete @azure-tools/typespec-azure-resource-manager/no-resource-delete-operation - - - - - Done -
3 AllTrackedResourcesMustHaveDelete @azure-tools/typespec-azure-resource-manager/no-resource-delete-operation - - - - - Done -
4 ApiHost TypeSpec structural - - - - - Done -
5 ApiVersionParameterRequired @azure-tools/typespec-azure-core/operation-missing-api-version, @azure-tools/typespec-azure-resource-manager/arm-resource-operation - - - - - Done -
6 APIVersionPattern @azure-tools/typespec-azure-resource-manager/arm-resource-invalid-version-format - - - - - Done -
7 ArmResourcePropertiesBag @azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property - - - - - Done -
8 ArrayMustHaveType TypeSpec structural - - - - - Done -
9 ArraySchemaMustHaveItems TypeSpec structural - - - - - Done -
10 AvoidAdditionalProperties @azure-tools/typespec-azure-resource-manager/arm-no-record - - - - - Done -
11 AvoidEmptyResponseSchema TypeSpec structural - - - - - Done -
12 AvoidNestedProperties TypeSpec structural - - - - - Done -
13 BodyTopLevelProperties @azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property - - - - - Done -
14 ControlCharactersAreNotAllowed TypeSpec structural - - - - - Done -
15 DefinitionsPropertiesNamesCamelCase @azure-tools/typespec-azure-core/casing-style - - - - - Done -
16 DeleteResponseCodes @azure-tools/typespec-azure-resource-manager/arm-delete-operation-response-codes, @azure-tools/typespec-azure-resource-manager/no-response-body - - - - - Done -
17 EnumMustHaveType TypeSpec structural - - - - - Done -
18 EnumMustNotHaveEmptyValue TypeSpec structural - - - - - Done -
19 EnumMustRespectType TypeSpec structural - - - - - Done -
20 EnumUniqueValue TypeSpec structural - - - - - Done -
21 HostParametersValidation TypeSpec structural - - - - - Done -
22 HttpsSupportedScheme TypeSpec structural - - - - - Done -
23 IntegerTypeMustHaveFormat TypeSpec structural - - - - - Done -
24 InvalidVerbUsed TypeSpec structural - - - - - Done -
25 LroLocationHeader @azure-tools/typespec-azure-resource-manager/lro-location-header - - - - - Done -
26 LroPostMustNotUseOriginalUriAsFinalState TypeSpec structural - - - - - Done -
27 LroStatusCodesReturnTypeSchema TypeSpec structural - - - - - Done -
28 MissingTypeObject TypeSpec structural - - - - - Done -
29 NamePropertyDefinitionInParameter TypeSpec structural - - - - - Done -
30 NextLinkPropertyMustExist TypeSpec structural - - - - - Done -
31 NoDuplicatePathsForScopeParameter TypeSpec structural - - - - - Done -
32 NonEmptyClientName TypeSpec structural - - - - - Done -
33 Nullable @azure-tools/typespec-azure-core/no-nullable - - - - - Done -
34 OperationIdRequired TypeSpec structural - - - - - Done -
35 OperationIdSingleUnderscore TypeSpec structural - - - - - Done -
36 OperationsAPIImplementation @azure-tools/typespec-azure-resource-manager/missing-operations-endpoint - - - - - Done -
37 OperationsApiTenantLevelOnly TypeSpec structural - - - - - Done -
38 OperationSummaryOrDescription @azure-tools/typespec-azure-core/documentation-required - - - - - Done -
39 ParameterDescription @azure-tools/typespec-azure-core/documentation-required - - - - - Done -
40 ParameterDescriptionRequired @azure-tools/typespec-azure-core/documentation-required - - - - - Done -
41 ParameterNotDefinedInGlobalParameters TypeSpec structural - - - - - Done -
42 ParameterOrder TypeSpec structural - - - - - Done -
43 ParametersOrder TypeSpec structural - - - - - Done -
44 PatchContentType TypeSpec structural - - - - - Done -
45 PathForResourceAction TypeSpec structural - - - - - Done -
46 PathResourceTypeNameCamelCase @azure-tools/typespec-azure-resource-manager/arm-resource-path-segment-invalid-chars - - - - - Done -
47 PropertiesTypeObjectNoDefinition @azure-tools/typespec-azure-resource-manager/no-empty-model - - - - - Done -
48 PropertyDescription @azure-tools/typespec-azure-core/documentation-required - - - - - Done -
49 ProvisioningStateMustBeReadOnly @azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state - - - - - Done -
50 ProvisioningStateSpecifiedForLROPatch TypeSpec structural - - - - - Done -
51 ProvisioningStateSpecifiedForLROPut TypeSpec structural - - - - - Done -
52 ProvisioningStateValidation @azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state - - - - - Done -
53 PutGetPatchResponseSchema @azure-tools/typespec-azure-resource-manager/arm-resource-operation-response - - - - - Done -
54 PutResponseCodes @azure-tools/typespec-azure-resource-manager/arm-put-operation-response-codes - - - - - Done -
55 RequestSchemaForTrackedResourcesMustHaveTags TypeSpec structural - - - - - Done -
56 ResourceNameRestriction @azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern - - - - - Done -
57 ResponseSchemaSpecifiedForSuccessStatusCode @azure-tools/typespec-azure-resource-manager/no-response-body - - - - - Done -
58 SchemaDescriptionOrTitle @azure-tools/typespec-azure-core/documentation-required - - - - - Done -
59 SchemaNamesConvention @azure-tools/typespec-azure-core/casing-style - - - - - Done -
60 SubscriptionIdParameterInOperations TypeSpec structural - - - - - Done -
61 SystemDataDefinitionsCommonTypes @azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property - - - - - Done -
62 TrackedExtensionResourcesAreNotAllowed @azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property - - - - - Done -
63 TrackedResourceBeyondsThirdLevel @azure-tools/typespec-azure-resource-manager/beyond-nesting-levels - - - - - Done -
64 UniqueClientParameterName TypeSpec structural - - - - - Done -
65 UniqueModelName TypeSpec structural - - - - - Done -
66 ValidResponseCodeRequired TypeSpec structural - - - - - Done -
67 XmsIdentifierValidation @azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers - - - - - Done -
68 XMSSecretInResponse @azure-tools/typespec-azure-resource-manager/secret-prop - - - - - Done -
69 EnumInsteadOfBoolean @azure-tools/typespec-azure-core 5265 2026-08-20 2026-08-31 catalinaperalta - Done -
70 QueryParametersInCollectionGet @azure-tools/typespec-azure-resource-manager 5318 2026-08-26 2026-09-02 timotheeguerin - Done -
71 ValidQueryParametersForPointOperations @azure-tools/typespec-azure-resource-manager 5273 2026-08-20 2026-09-03 catalinaperalta, timotheeguerin - Done -
72 ParametersInPointGet @azure-tools/typespec-azure-resource-manager 5273 2026-08-20 2026-09-03 catalinaperalta, timotheeguerin - Done -
73 NonApplicationJsonType @azure-tools/typespec-azure-resource-manager 5302 2026-08-25 2026-09-03 timotheeguerin - Done -
74 GetCollectionOnlyHasValueAndNextLink @azure-tools/typespec-azure-resource-manager 5358 2026-09-01 2026-09-04 timotheeguerin - Done -
75 XmsPageableForListCalls @azure-tools/typespec-azure-resource-manager 5379 2026-09-02 2026-09-07 timotheeguerin - Done -
76 ParametersInPost @azure-tools/typespec-azure-resource-manager 5281 2026-08-24 2026-09-09 catalinaperalta, timotheeguerin - Done -
77 TenantLevelAPIsNotAllowed @azure-tools/typespec-azure-resource-manager 5420 2026-09-08 2026-09-09 timotheeguerin - Done -
78 GetInOperationName @azure-tools/typespec-client-generator-core 5364 2026-09-01 2026-09-15 timotheeguerin - Done -
79 PutInOperationName @azure-tools/typespec-client-generator-core 5457 2026-09-11 2026-09-15 timotheeguerin - Done -
80 ParametersSchemaAsTypeObject @azure-tools/typespec-azure-resource-manager 5361 2026-09-01 2026-09-18 timotheeguerin - Done -
81 GuidUsage @azure-tools/typespec-azure-resource-manager 5336 2026-08-28 - - No Done no need
82 AvoidAnonymousParameter @azure-tools/typespec-azure-core, @azure-tools/typespec-azure-resource-manager 5522 2026-09-20 2026-10-15 timotheeguerin - Done -
83 CollectionObjectPropertiesNaming @azure-tools/typespec-azure-resource-manager 5582 2026-09-29 2026-10-15 timotheeguerin - Done -
84 SummaryAndDescriptionMustNotBeSame @azure-tools/typespec-azure-core 5507 2026-09-17 2026-10-16 timotheeguerin - Done -
85 SubscriptionsAndResourceGroupCasing @azure-tools/typespec-azure-resource-manager 5681 2026-10-13 2026-10-16 timotheeguerin - Done -
86 AllResourcesMustHaveGetOperation @azure-tools/typespec-azure-resource-manager 5654 2026-10-09 2026-10-19 timotheeguerin - Done -
87 EvenSegmentedPathForPutOperation @azure-tools/typespec-azure-resource-manager 5550 2026-09-23 2026-10-20 timotheeguerin - Done -
88 TagsAreNotAllowedForProxyResources @azure-tools/typespec-azure-resource-manager 5502 2026-09-17 2026-10-21 markcowl, timotheeguerin - Done -
89 NoErrorCodeResponses @azure-tools/typespec-azure-resource-manager 5556 2026-09-23 2026-10-21 timotheeguerin - Done -
90 GetResponseCodes @azure-tools/typespec-azure-resource-manager 5556 2026-09-23 2026-10-21 timotheeguerin - Done -
91 ExtensionResourcePathPattern @azure-tools/typespec-azure-resource-manager 5684 2026-10-13 2026-10-21 timotheeguerin - Done -
92 PatchInOperationName @azure-tools/typespec-client-generator-core 5673 2026-10-12 2026-10-22 timotheeguerin - Done -
93 DeleteInOperationName @azure-tools/typespec-client-generator-core 5673 2026-10-12 2026-10-22 timotheeguerin - Done -
94 LroErrorContent @azure-tools/typespec-azure-resource-manager 5451 2026-09-11 2026-10-29 markcowl, timotheeguerin - Done -
95 PostResponseCodes @azure-tools/typespec-azure-resource-manager 5638 2026-10-08 2026-10-29 timotheeguerin - Done -
96 XmsExamplesRequired - 5398 - - - - Hold on -
97 LatestVersionOfCommonTypesMustBeUsed @azure-tools/typespec-azure-resource-manager 5271 2026-08-20 - - No Doing -
98 PatchBodyParametersSchema @azure-tools/typespec-azure-resource-manager 5294 2026-08-25 - - Yes Doing -
99 UnSupportedPatchProperties @azure-tools/typespec-azure-resource-manager 5294 2026-08-25 - - Yes Doing -
100 ConsistentResponseSchemaForPut @azure-tools/typespec-azure-resource-manager 5423 2026-09-08 - - No Doing -
101 ConsistentPatchProperties @azure-tools/typespec-azure-resource-manager 5294 2026-08-25 - - Yes Doing -
102 PatchPropertiesCorrespondToPutProperties @azure-tools/typespec-azure-resource-manager 5481 2026-09-15 - - No Doing -
103 PutRequestResponseSchemeArm @azure-tools/typespec-azure-resource-manager 5423 2026-09-08 - - No Doing -
104 XmsResourceInPutResponse @azure-tools/typespec-azure-resource-manager 5423 2026-09-08 - - No Doing -
105 RepeatedPathInfo @azure-tools/typespec-azure-resource-manager 5526 2026-09-21 - - No Doing -
106 AvoidAnonymousTypes @azure-tools/typespec-azure-core, @azure-tools/typespec-azure-resource-manager 5551 2026-09-23 - - No Doing -
107 OperationIdNounConflictingModelNames @azure-tools/typespec-client-generator-core 5591 2026-09-29 - timotheeguerin No Doing -
108 LroExtension @azure-tools/typespec-azure-resource-manager 5652 2026-10-09 - - No Doing -
109 DescriptionMustNotBeNodeName @azure-tools/typespec-azure-core 5663 2026-10-10 - - No Doing -
110 DescriptiveDescriptionRequired @azure-tools/typespec-azure-core 5663 2026-10-10 - - No Doing -
111 XMSLongRunningOperationProperty @azure-tools/typespec-azure-resource-manager 5652 2026-10-09 - - No Doing -
112 ImplementPrivateEndpointAPIs @azure-tools/typespec-azure-resource-manager 5766 2026-10-19 - - No Doing -
113 DeleteResponseBodyEmpty @azure-tools/typespec-azure-resource-manager 5866 2026-10-28 - timotheeguerin No Doing -
114 GetOperationMustNotBeLongRunning @azure-tools/typespec-azure-core, @azure-tools/typespec-azure-resource-manager, @azure-tools/typespec-client-generator-core 5889 2026-10-29 - - No Doing -
115 PathContainsResourceGroup @azure-tools/typespec-azure-resource-manager 5859 2026-10-28 - timotheeguerin No Doing -
116 SystemDataInPropertiesBag @azure-tools/typespec-azure-resource-manager 5892 2026-10-29 - - No Doing -
117 ReservedNamesInPropertiesBag - - - - - - To do -
118 AvoidMsdnReferences C1 - Documentation links - - - - - To do -
119 InvalidSkuModel - - - - - - To do -
120 ListInOperationName - - - - - - To do -
121 LocationMustHaveXmsMutability - - - - - - To do -
122 LongRunningOperationsOptionsValidator - - - - - - To do -
123 LroPatch202 M4 - PATCH response codes - - - - - To do -
124 PatchResponseCodes M4 - PATCH response codes - - - - - To do -
125 MissingSegmentsInNestedResourceListOperation - - - - - - To do -
126 MissingXmsErrorResponse - - - - - - To do -
127 MutabilityWithReadOnly - - - - - - To do -
128 NestedResourcesMustHaveListOperation C6 - Required collections - - - - - To do -
129 TopLevelResourcesListByResourceGroup C6 - Required collections - - - - - To do -
130 OperationIdNounVerb - - - - - - To do -
131 OperationsApiResponseSchema C8 - Canonical operations response - - - - - To do -
132 OperationsApiSchemaUsesCommonTypes C8 - Canonical operations response - - - - - To do -
133 PageableRequires200Response C7 - Pagination - - - - - To do -
134 XmsPageableMustHaveCorrespondingResponse C7 - Pagination - - - - - To do -
135 ParameterNotUsingCommonTypes - - - - - - To do -
136 PatchIdentityProperty C4 - PATCH envelope - - - - - To do -
137 PatchSkuProperty C4 - PATCH envelope - - - - - To do -
138 PathContainsResourceType - - - - - - To do -
139 PathForNestedResource - - - - - - To do -
140 PathForTrackedResourceTypes - - - - - - To do -
141 PathResourceProviderNamePascalCase - - - - - - To do -
142 PostOperationIdContainsUrlVerb - - - - - - To do -
143 PreviewVersionOverOneYear - - - - - - To do -
144 PutRequestResponseScheme - - - - - - To do -
145 RequestBodyMustExistForPutPatch - - - - - - To do -
146 ReservedResourceNamesModelAsEnum - - - - - - To do -
147 ResourceHasXMsResourceEnabled - - - - - - To do -
148 TrackedResourcePatchOperation M5 - Tracked-resource lifecycle - - - - - To do -
149 TrackedResourcesMustHavePut M5 - Tracked-resource lifecycle - - - - - To do -
150 ValidFormats - - - - - - To do -
151 XmsPathsMustOverloadPaths - - - - - - To do -
152 docLinkLocale C1 - Documentation links - - - - - Need investigation -
153 TopLevelResourcesListBySubscription C6 - Required collections - - - - - Need investigation -
154 PageableOperation C7 - Pagination - - - - - Need investigation -
155 AzureResourceTagsSchema - - - - - - Need investigation -
156 DefaultErrorResponseSchema - - - - - - Need investigation -
157 DefaultInEnum - - - - - - Need investigation -
158 DeleteMustNotHaveRequestBody M3 - Request-body prohibition - - - - - Need investigation -
159 GetMustNotHaveRequestBody M3 - Request-body prohibition - - - - - Need investigation -
160 DeleteOperationResponses - - - - - - Need investigation -
161 DeprecatedXmsCodeGenerationSetting M2 - Legacy SDK settings - - - - - Need investigation -
162 LicenseHeaderMustNotBeSpecified M2 - Legacy SDK settings - - - - - Need investigation -
163 GetCollectionResponseSchema - - - - - - Need investigation -
164 LroWithOriginalUriAsFinalState - - - - - - Need investigation -
165 MissingDefaultResponse C5 - Default-error response - - - - - Need investigation -
166 RequiredDefaultResponse C5 - Default-error response - - - - - Need investigation -
167 PathContainsSubscriptionId - - - - - - Need investigation -
168 PathResourceProviderMatchNamespace - - - - - - Need investigation -
169 PrivateEndpointResourceSchemaValidation - - - - - - Need investigation -
170 RequiredPropertiesMissingInResourceModel - - - - - - Need investigation -
171 RequiredReadOnlySystemData - - - - - - Need investigation -
172 Rpaas_ResourceProvisioningState - - - - - - Need investigation -
173 SecurityDefinitionsStructure - - - - - - Need investigation -
174 UniqueXmsEnumName - - - - - - Need investigation -
175 UniqueXmsExample - - - - - - Need investigation -
176 XmsClientNameParameter M1 - Redundant client name - - - - - Need investigation -
177 XmsClientNameProperty M1 - Redundant client name - - - - - Need investigation -
178 XmsClientName M1 - Redundant client name - - - - - Need investigation -
179 XmsEnumValidation - - - - - - Need investigation -
180 XmsPageableListByRGAndSubscriptions - - - - - - Need investigation -
181 XmsParameterLocation - - - - - - Need investigation -

Migration groups: Linked labels appear below package/rule information, or alone if unknown. M = merge/subsumption candidate; C = conditional/shared-owner family. Grouping means coordinated work, not accepted coverage, completion, or enablement. See the consolidation analysis and the ordering policy below.

AI update notes

Test tracker destination

This copy records migration data only in msyyc/typespec-azure#2. Keep $repo = 'Azure/typespec-azure' for PR discovery, pinned PRs, changed files, reviews, comments, commits, and PR links. Use $issueRepo = 'msyyc/typespec-azure' and $issueNumber = 2 for tracker reads, concurrency checks, writes, and saved-body verification. Running with -Apply updates this test issue only; it must not update Azure#5274. All discovery, group membership, metadata, protected-row, and notes-preservation rules remain unchanged. The status-first ordering policy below applies to both the table and reusable script.

Keep exactly 181 unique Swagger rules and ten columns. Refresh PR metadata only for rows not already Done or Hold on. Preserve all data cells of those protected rows and every row's existing notes; only position/ID may change. Never derive notes from PR content.

Discovery and consolidation pins

Use titles [Swagger Linter Migration] <SwaggerRuleName> -> <TypeSpecRuleName> (spaces around -> recommended). Search using the original Swagger name, accepting legacy titles too; require whitespace, ->, or end-of-title after the name, and exclude (origin). Do not rename Swagger entries or infer packages from titles.

Consult $pinnedPrs before title discovery. Fetch pinned PRs directly, bypassing title matching, but never bypass protected rows or other validation. The following mappings are authoritative:

Swagger rules PR / native owner Provenance and boundaries
ConsistentResponseSchemaForPut, PutRequestResponseSchemeArm, XmsResourceInPutResponse #5423 / put-resource-schema-consistency Supersedes Azure#5493 and Azure#5503 (closed as superseded). Not enabled yet. Preserve arm-put-operation-response-codes (complete PUT status set) and arm-resource-operation-response (canonical resource across lifecycle operations); intra-PUT consistency cannot replace them.
PatchBodyParametersSchema, ConsistentPatchProperties, UnSupportedPatchProperties #5294 / no-unsafe-patch-body-properties Azure#5456 and Azure#5384 remain open, proposed for supersession only after coverage acceptance; this mapping neither accepts coverage nor authorizes closure. PatchPropertiesCorrespondToPutProperties (Azure#5481) stays separate.
DescriptionMustNotBeNodeName, DescriptiveDescriptionRequired #5663 / azure-core documentation-required Absorbs implementation/tests from Azure#5674, closed as superseded.
NoErrorCodeResponses, GetResponseCodes #5556 / ARM use-standard-response-codes Absorbs implementation/tests from Azure#5585, closed as superseded.
LroExtension, XMSLongRunningOperationProperty #5652 / ARM lro-missing-polling-metadata Absorbs official policy/unique tests from Azure#5709, closed as superseded. Preserve ARM response-code/header-presence rules. A disabled central entry does not mean universal disablement: ARM library all includes the registered rule.
PutInOperationName, PatchInOperationName, DeleteInOperationName #5673 / TCGC use-correct-verb Azure#5665 (PATCH) is closed as superseded. Azure#5457 (PUT) genuinely merged: keep that Done row's original PR, merge date, and all cells. Only eligible PATCH/DELETE rows refresh from Azure#5673.

Do not fall back to superseded PRs or equate closure/supersession with completion. Eligible mapped rows are Doing while their replacement is open and Done only after it merges. Consolidation implies neither CI/review acceptance, equivalent coverage, retirement of existing checks, nor automatic enablement.

Grouping and ordering

Reuse tsp lib / migration group; keep notes rightmost. Infer package information from changed files under packages/typespec-azure-core, packages/typespec-azure-resource-manager, and packages/typespec-client-generator-core, using corresponding @azure-tools/ package names. Preserve existing package/rule information when no PR is found. Append each configured linked group with <br>, or show the group alone when the library is unknown. Never infer a library/native rule from a group label.

$migrationGroups and $reviewedRules define membership/order by Swagger name, never current row ID: 11 groups / 25 rules from M1-M5, C1, C4-C8 in the linked analysis. M means merge/subsumption candidate; C means conditional/shared-owner family, not necessarily one lint. Optional C5 member DefaultErrorResponseSchema stays ungrouped; C2 route helpers and C3 independent naming predicates are not merger groups.

Sort all 181 rules by status first: Done, Hold on, Doing, To do, Need investigation. Each status must form one contiguous block, including all Doing rows regardless of reviewed-rule membership. Within each status, keep other rules first in their existing relative order, followed by reviewed rules in name-keyed group/original order. Group blocks anchor at the earliest original member; preserve original member/ungrouped order within that status. Status takes priority over reviewed sections and group adjacency: split a migration group across status blocks when members have different statuses. Renumber IDs. Preserve group labels during metadata refresh. Future group edits affecting protected cells require explicit user authorization.

Metadata and safety

For valid PRs, fill PR link, creation/merge dates (UTC), libraries, approvals, and status. For open PRs, use each eligible reviewer's latest decisive APPROVED/CHANGES_REQUESTED/DISMISSED review; comments do not revoke approvals. Exclude the author and bots; use real User aliases.

Set new-comment to - when merged. Otherwise compare head commit committer time with top-level and inline comments' created_at and nonempty review summaries' submitted_at; Yes if eligible feedback is later, otherwise No. Check all three API surfaces, not PR updatedAt or gh pr view comments alone.

Read issue JSON via gh api, joining CLI output with newlines before ConvertFrom-Json. Never stringify a multiline body array. Write UTF-8 without BOM. Accept LF/CRLF rows; require 181 unique ten-cell rows, consecutive IDs, preserved protected cells/notes, correct group annotations/layout, complete files, and stable PR snapshots. Abort on ambiguity, incomplete data, or concurrent issue edits; verify the exact saved body. Preserve all non-table content during routine refresh.

Reusable PowerShell update script

Requires PowerShell 7+ and an authenticated GitHub CLI (gh). Save the script below as refresh-arm-linter-issue.ps1. The authenticated account needs issue-edit permission when applying changes.

  • Preview without changing the issue: pwsh -File .\refresh-arm-linter-issue.ps1
  • Refresh the issue: pwsh -File .\refresh-arm-linter-issue.ps1 -Apply

The script sorts every row by status first, keeping all Doing rows together, then applies the name-keyed grouping/order policy within each status (including group annotations alongside inferred packages), preserves all Done and Hold on row data (apart from sorting and renumbering IDs), preserves existing notes for every row, and preserves all content outside the table (including this section). It checks all three feedback surfaces and stops on ambiguous PRs or incomplete data. Dates are UTC. It also recognizes @azure-tools/typespec-client-generator-core when that package contains the implementation. Review the notes before running in case the update policy changes.

# Requires PowerShell 7+ and an authenticated GitHub CLI (gh).
# Preview: pwsh -File .\refresh-arm-linter-issue.ps1
# Update:  pwsh -File .\refresh-arm-linter-issue.ps1 -Apply
[CmdletBinding()]
param([switch]$Apply)

$ErrorActionPreference = 'Stop'
$repo = 'Azure/typespec-azure'
$issueRepo = 'msyyc/typespec-azure'
$issueNumber = 2
$expectedRows = 181
$statusOrder = @{ 'Done' = 0; 'Hold on' = 1; 'Doing' = 2; 'To do' = 3; 'Need investigation' = 4 }
$pinnedPrs = @{
    'ConsistentResponseSchemaForPut' = 5423
    'PutRequestResponseSchemeArm' = 5423
    'XmsResourceInPutResponse' = 5423
    'PatchBodyParametersSchema' = 5294
    'ConsistentPatchProperties' = 5294
    'UnSupportedPatchProperties' = 5294
    'DescriptionMustNotBeNodeName' = 5663
    'DescriptiveDescriptionRequired' = 5663
    'NoErrorCodeResponses' = 5556
    'GetResponseCodes' = 5556
    'LroExtension' = 5652
    'XMSLongRunningOperationProperty' = 5652
    'PutInOperationName' = 5673
    'PatchInOperationName' = 5673
    'DeleteInOperationName' = 5673
}

$migrationGroups = @(
    @{ Id = 'M1'; Label = 'Redundant client name'; Members = @('XmsClientNameParameter', 'XmsClientNameProperty', 'XmsClientName') }
    @{ Id = 'M2'; Label = 'Legacy SDK settings'; Members = @('DeprecatedXmsCodeGenerationSetting', 'LicenseHeaderMustNotBeSpecified') }
    @{ Id = 'M3'; Label = 'Request-body prohibition'; Members = @('GetMustNotHaveRequestBody', 'DeleteMustNotHaveRequestBody') }
    @{ Id = 'M4'; Label = 'PATCH response codes'; Members = @('PatchResponseCodes', 'LroPatch202') }
    @{ Id = 'M5'; Label = 'Tracked-resource lifecycle'; Members = @('TrackedResourcesMustHavePut', 'TrackedResourcePatchOperation') }
    @{ Id = 'C1'; Label = 'Documentation links'; Members = @('AvoidMsdnReferences', 'docLinkLocale') }
    @{ Id = 'C4'; Label = 'PATCH envelope'; Members = @('PatchIdentityProperty', 'PatchSkuProperty') }
    @{ Id = 'C5'; Label = 'Default-error response'; Members = @('RequiredDefaultResponse', 'MissingDefaultResponse') }
    @{ Id = 'C6'; Label = 'Required collections'; Members = @('NestedResourcesMustHaveListOperation', 'TopLevelResourcesListByResourceGroup', 'TopLevelResourcesListBySubscription') }
    @{ Id = 'C7'; Label = 'Pagination'; Members = @('PageableOperation', 'PageableRequires200Response', 'XmsPageableMustHaveCorrespondingResponse') }
    @{ Id = 'C8'; Label = 'Canonical operations response'; Members = @('OperationsApiResponseSchema', 'OperationsApiSchemaUsesCommonTypes') }
)
$reviewedRules = @(
    'AvoidMsdnReferences'
    'DeleteResponseBodyEmpty'
    'GetOperationMustNotBeLongRunning'
    'InvalidSkuModel'
    'ListInOperationName'
    'LocationMustHaveXmsMutability'
    'LongRunningOperationsOptionsValidator'
    'LroPatch202'
    'MissingSegmentsInNestedResourceListOperation'
    'MissingXmsErrorResponse'
    'MutabilityWithReadOnly'
    'NestedResourcesMustHaveListOperation'
    'OperationIdNounVerb'
    'OperationsApiResponseSchema'
    'OperationsApiSchemaUsesCommonTypes'
    'PageableRequires200Response'
    'ParameterNotUsingCommonTypes'
    'PatchIdentityProperty'
    'PatchResponseCodes'
    'PatchSkuProperty'
    'PathContainsResourceGroup'
    'PathContainsResourceType'
    'PathForNestedResource'
    'PathForTrackedResourceTypes'
    'PathResourceProviderNamePascalCase'
    'PostOperationIdContainsUrlVerb'
    'PreviewVersionOverOneYear'
    'PutRequestResponseScheme'
    'RequestBodyMustExistForPutPatch'
    'ReservedResourceNamesModelAsEnum'
    'ResourceHasXMsResourceEnabled'
    'TopLevelResourcesListByResourceGroup'
    'TrackedResourcePatchOperation'
    'TrackedResourcesMustHavePut'
    'ValidFormats'
    'XmsPageableMustHaveCorrespondingResponse'
    'XmsPathsMustOverloadPaths'
    'AzureResourceTagsSchema'
    'DefaultErrorResponseSchema'
    'DefaultInEnum'
    'DeleteMustNotHaveRequestBody'
    'DeleteOperationResponses'
    'DeprecatedXmsCodeGenerationSetting'
    'docLinkLocale'
    'GetCollectionResponseSchema'
    'GetMustNotHaveRequestBody'
    'LicenseHeaderMustNotBeSpecified'
    'LroWithOriginalUriAsFinalState'
    'MissingDefaultResponse'
    'PageableOperation'
    'PathContainsSubscriptionId'
    'PathResourceProviderMatchNamespace'
    'PrivateEndpointResourceSchemaValidation'
    'RequiredDefaultResponse'
    'RequiredPropertiesMissingInResourceModel'
    'RequiredReadOnlySystemData'
    'Rpaas_ResourceProvisioningState'
    'SecurityDefinitionsStructure'
    'SystemDataInPropertiesBag'
    'TopLevelResourcesListBySubscription'
    'UniqueXmsEnumName'
    'UniqueXmsExample'
    'XmsClientNameParameter'
    'XmsClientNameProperty'
    'XmsEnumValidation'
    'XmsPageableListByRGAndSubscriptions'
    'XmsParameterLocation'
    'XmsClientName'
)
$reviewedRank = @{}
for ($i = 0; $i -lt $reviewedRules.Count; $i++) {
    if ($reviewedRank.ContainsKey($reviewedRules[$i])) { throw 'Duplicate reviewed rule.' }
    $reviewedRank[$reviewedRules[$i]] = $i
}
$groupByRule = @{}
$groupRank = @{}
foreach ($group in $migrationGroups) {
    $anchorRank = ($group.Members | ForEach-Object {
        if (-not $reviewedRank.ContainsKey($_)) { throw "Unknown group member: $_" }
        $reviewedRank[$_]
    } | Measure-Object -Minimum).Minimum
    foreach ($rule in $group.Members) {
        if ($groupByRule.ContainsKey($rule)) { throw "Duplicate group membership: $rule" }
        $groupByRule[$rule] = $group
        $groupRank[$rule] = $anchorRank
    }
}

function Format-MigrationLibrary([string]$Rule, [string]$Library) {
    if (-not $groupByRule.ContainsKey($Rule)) { return $Library }
    $group = $groupByRule[$Rule]
    $annotation = "[$($group.Id) - $($group.Label)][migration-$($group.Id)]"
    if ($Library -ceq $annotation) { return $annotation }
    if ($Library.EndsWith("<br>$annotation", [StringComparison]::Ordinal)) { return $Library }
    if ($Library -eq '-' -or [string]::IsNullOrWhiteSpace($Library)) { return $annotation }
    "$Library<br>$annotation"
}

function Get-OrderedRows([object[]]$Rows) {
    $Rows | Sort-Object { $statusOrder[$_.Cells[8]] }, {
        if ($reviewedRank.ContainsKey($_.Rule)) { 1 } else { 0 }
    }, {
        if ($groupRank.ContainsKey($_.Rule)) { $groupRank[$_.Rule] }
        elseif ($reviewedRank.ContainsKey($_.Rule)) { $reviewedRank[$_.Rule] }
        else { $_.OriginalId }
    }, {
        if ($reviewedRank.ContainsKey($_.Rule)) { $reviewedRank[$_.Rule] }
        else { $_.OriginalId }
    }
}

function Assert-MigrationLayout([object[]]$Rows) {
    foreach ($rule in $reviewedRules) {
        if ($rule -notin $Rows.Rule) { throw "Missing reviewed rule: $rule" }
    }
    foreach ($row in $Rows) {
        if ($row.Cells[2] -cne (Format-MigrationLibrary $row.Rule $row.Cells[2])) {
            throw "Missing migration-group annotation: $($row.Rule)"
        }
    }
    for ($i = 1; $i -lt $Rows.Count; $i++) {
        if ($statusOrder[$Rows[$i].Cells[8]] -lt $statusOrder[$Rows[$i - 1].Cells[8]]) {
            throw 'Statuses must form contiguous blocks: Done, Hold on, Doing, To do, Need investigation.'
        }
    }
    $ordered = @(Get-OrderedRows $Rows)
    if (($Rows.Rule -join '|') -cne ($ordered.Rule -join '|')) {
        throw 'Incorrect status-first/migration-group ordering.'
    }
}

function Get-GitHubJson([string[]]$Arguments) {
    $raw = & gh @Arguments
    if ($LASTEXITCODE -ne 0) { throw "GitHub command failed: gh $($Arguments -join ' ')" }
    ($raw -join "`n") | ConvertFrom-Json
}

function Get-Api([string]$Path, [switch]$Paginate) {
    if ($Paginate) {
        $pages = Get-GitHubJson @('api', $Path, '--paginate', '--slurp')
        foreach ($page in $pages) { foreach ($item in $page) { $item } }
    } else {
        Get-GitHubJson @('api', $Path)
    }
}

function Test-EligibleUser($User, [string]$Author) {
    $User.type -eq 'User' -and $User.login -ne $Author -and $User.login -notmatch '\[bot\]$'
}

function Get-Rows([string]$Body) {
    foreach ($line in ($Body -split "`n")) {
        if ($line -notmatch '^\| \d+ \|') { continue }
        if ($line -notmatch '^\|(?:[^|\r\n]*\|){10}\r?$') { throw 'Malformed table row.' }
        $cells = @($line.Split('|')[1..10] | ForEach-Object { $_.Trim() })
        if (-not $statusOrder.ContainsKey($cells[8])) { throw "Unknown status: $($cells[8])" }
        $rule = if ($cells[1] -match '^\[([^\]]+)\]') { $Matches[1] } else { $cells[1] }
        [pscustomobject]@{ OriginalId = [int]$cells[0]; Rule = $rule; Cells = $cells }
    }
}

function Assert-Table([object[]]$Rows) {
    if ($Rows.Count -ne $expectedRows -or @($Rows.Rule | Sort-Object -Unique).Count -ne $expectedRows) {
        throw "Expected exactly $expectedRows unique rules."
    }
    for ($i = 0; $i -lt $Rows.Count; $i++) {
        if ($Rows[$i].OriginalId -ne $i + 1) { throw 'IDs must be consecutive from 1.' }
    }
}

$issue = Get-Api "repos/$issueRepo/issues/$issueNumber"
$originalBody = $issue.body
$rows = @(Get-Rows $originalBody)
Assert-Table $rows
$allPrs = @(Get-GitHubJson @(
    'pr', 'list', '--repo', $repo, '--state', 'all',
    '--search', '"[Swagger Linter Migration]" in:title',
    '--limit', '1000', '--json', 'number,title'
))
if ($allPrs.Count -ge 1000) { throw 'PR search may be truncated; narrow or paginate the search.' }
$candidates = @($allPrs | Where-Object { $_.title -notmatch '\(origin\)' })
$changes = [Collections.Generic.List[object]]::new()

foreach ($row in $rows) {
    if ($row.Cells[8] -in @('Done', 'Hold on')) { continue }
    $row.Cells[2] = Format-MigrationLibrary $row.Rule $row.Cells[2]
    # Match the original Swagger name in legacy titles and OldRule -> new-rule mappings.
    $pattern = '^\[Swagger Linter Migration\]\s+' + [regex]::Escape($row.Rule) + '(?=\s|->|$)'
    $isPinned = $pinnedPrs.ContainsKey($row.Rule)
    if ($isPinned) {
        $number = $pinnedPrs[$row.Rule]
    } else {
        $matching = @($candidates | Where-Object { $_.title -match $pattern })
        if ($matching.Count -gt 1) { throw "Multiple matching PRs for $($row.Rule); resolve manually." }
        if ($matching.Count -eq 0) {
            if ($row.Cells[3] -match '/pull/\d+') {
                throw "Existing PR for $($row.Rule) was not found in search; investigate before updating."
            }
            continue
        }
        $number = $matching[0].number
    }

    $pr = Get-Api "repos/$repo/pulls/$number"
    if (-not $isPinned -and ($pr.title -notmatch $pattern -or $pr.title -match '\(origin\)')) {
        throw "PR #$number title changed."
    }
    if (-not $pr.merged -and $pr.state -ne 'open') {
        throw "PR #$number is closed without merging; resolve its table status manually."
    }
    $files = @(Get-Api "repos/$repo/pulls/$number/files?per_page=100" -Paginate)
    if ($files.Count -ne $pr.changed_files) { throw "Incomplete changed-file list for PR #$number." }
    $libs = @(
        foreach ($package in @('typespec-azure-core', 'typespec-azure-resource-manager', 'typespec-client-generator-core')) {
            if ($files.filename -like "packages/$package/*") { "@azure-tools/$package" }
        }
    )
    if (-not $libs.Count) { throw "Unrecognized library in PR #$number; extend the package mapping." }

    $reviews = @(Get-Api "repos/$repo/pulls/$number/reviews?per_page=100" -Paginate)
    # A later change request or dismissal supersedes an approval; a comment does not.
    $approvals = @($reviews | Where-Object {
        (Test-EligibleUser $_.user $pr.user.login) -and
        $_.state -in @('APPROVED', 'CHANGES_REQUESTED', 'DISMISSED')
    } | Group-Object { $_.user.login } | ForEach-Object {
        $last = $_.Group | Sort-Object { [DateTimeOffset]$_.submitted_at }, id | Select-Object -Last 1
        if ($last.state -eq 'APPROVED') { $last.user.login }
    } | Sort-Object -Unique)

    $feedbackFlag = '-'
    if (-not $pr.merged) {
        $comments = @(Get-Api "repos/$repo/issues/$number/comments?per_page=100" -Paginate)
        $inline = @(Get-Api "repos/$repo/pulls/$number/comments?per_page=100" -Paginate)
        $head = Get-Api "repos/$repo/commits/$($pr.head.sha)"
        $latest = [DateTimeOffset]$head.commit.committer.date
        $feedback = @(
            @($comments) + @($inline) | Where-Object {
                (Test-EligibleUser $_.user $pr.user.login) -and
                [DateTimeOffset]$_.created_at -gt $latest
            }
            $reviews | Where-Object {
                (Test-EligibleUser $_.user $pr.user.login) -and
                -not [string]::IsNullOrWhiteSpace($_.body) -and
                $_.submitted_at -and [DateTimeOffset]$_.submitted_at -gt $latest
            }
        )
        $feedbackFlag = if ($feedback.Count) { 'Yes' } else { 'No' }
    }
    $fresh = Get-Api "repos/$repo/pulls/$number"
    if ($fresh.head.sha -ne $pr.head.sha -or $fresh.state -ne $pr.state -or
        $fresh.merged -ne $pr.merged -or $fresh.title -cne $pr.title -or $fresh.body -cne $pr.body) {
        throw "PR #$number changed while reading; rerun."
    }

    $before = $row.Cells[2..9] -join ' | '
    $row.Cells[2] = Format-MigrationLibrary $row.Rule ($libs -join ', ')
    $row.Cells[3] = "[$number](https://github.com/$repo/pull/$number)"
    $row.Cells[4] = ([DateTimeOffset]$pr.created_at).ToString('yyyy-MM-dd')
    $row.Cells[5] = if ($pr.merged_at) { ([DateTimeOffset]$pr.merged_at).ToString('yyyy-MM-dd') } else { '-' }
    $row.Cells[6] = if ($approvals.Count) { $approvals -join ', ' } else { '-' }
    $row.Cells[7] = $feedbackFlag
    $row.Cells[8] = if ($pr.merged) { 'Done' } else { 'Doing' }
    $after = $row.Cells[2..9] -join ' | '
    if ($before -cne $after) {
        $changes.Add([pscustomobject]@{ Rule = $row.Rule; Before = $before; After = $after })
    }
}

$sorted = @(Get-OrderedRows $rows)
$index = 0
$newLines = foreach ($line in ($originalBody -split "`n")) {
    if ($line -match '^\| \d+ \|') {
        $sorted[$index].Cells[0] = ($index + 1).ToString()
        '| ' + ($sorted[$index].Cells -join ' | ') + ' |'
        $index++
    } else { $line }
}
$newBody = $newLines -join "`n"
$newRows = @(Get-Rows $newBody)
Assert-Table $newRows
foreach ($old in @(Get-Rows $originalBody)) {
    $new = $newRows | Where-Object Rule -eq $old.Rule
    if (-not $new) { throw "Lost rule: $($old.Rule)" }
    if ($old.Cells[8] -in @('Done', 'Hold on') -and ($old.Cells[1..8] -join '|') -cne ($new.Cells[1..8] -join '|')) {
        throw "Modified a protected Done/Hold on row: $($old.Rule)"
    }
    if ($old.Cells[9] -cne $new.Cells[9]) {
        throw "Modified preserved notes: $($old.Rule)"
    }
}
Assert-MigrationLayout $newRows
$changes | Format-List
$newRows | Group-Object { $_.Cells[8] } | Select-Object Name, Count | Format-Table
if ($newBody -ceq $originalBody) { Write-Output 'Already up to date; no edit needed.'; return }
if (-not $Apply) { Write-Output 'Preview only. Rerun with -Apply to update the issue.'; return }

$current = Get-Api "repos/$issueRepo/issues/$issueNumber"
if ($current.body -cne $originalBody) { throw 'Issue changed during refresh; rerun to avoid overwriting edits.' }
$tempFile = [IO.Path]::GetTempFileName()
try {
    [IO.File]::WriteAllText($tempFile, $newBody, [Text.UTF8Encoding]::new($false))
    & gh issue edit $issueNumber --repo $issueRepo --body-file $tempFile
    if ($LASTEXITCODE -ne 0) { throw 'Issue update failed.' }
    $saved = Get-Api "repos/$issueRepo/issues/$issueNumber"
    if ($saved.body -cne $newBody) { throw 'Saved issue differs from proposed body; inspect it before retrying.' }
    Assert-Table @(Get-Rows $saved.body)
    Assert-MigrationLayout @(Get-Rows $saved.body)
    Write-Output "Updated https://github.com/$issueRepo/issues/$issueNumber"
} finally {
    Remove-Item -LiteralPath $tempFile
}

Activity

  1. self-assigned this
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions