-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Report diagnostics on
@clientName
conflicts (#1119)
Resolves #1096 --------- Co-authored-by: Timothee Guerin <[email protected]>
- Loading branch information
1 parent
a7118e1
commit d635570
Showing
7 changed files
with
547 additions
and
16 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
.chronus/changes/throw-client-name-conflicts-2024-6-5-13-7-58.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
changeKind: feature | ||
packages: | ||
- "@azure-tools/typespec-client-generator-core" | ||
--- | ||
|
||
Report diagnostics on `@clientName` conflicts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 153 additions & 7 deletions
160
packages/typespec-client-generator-core/src/validate.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,158 @@ | ||
import { Program } from "@typespec/compiler"; | ||
import { createTCGCContext } from "./internal-utils.js"; | ||
import { getAllModelsWithDiagnostics } from "./types.js"; | ||
import { | ||
AugmentDecoratorStatementNode, | ||
DecoratorExpressionNode, | ||
Enum, | ||
EnumMember, | ||
Interface, | ||
Model, | ||
ModelProperty, | ||
Namespace, | ||
Operation, | ||
Program, | ||
Scalar, | ||
SyntaxKind, | ||
Type, | ||
Union, | ||
UnionVariant, | ||
} from "@typespec/compiler"; | ||
import { DuplicateTracker } from "@typespec/compiler/utils"; | ||
import { getClientNameOverride } from "./decorators.js"; | ||
import { AllScopes, clientNameKey, createTCGCContext, TCGCContext } from "./internal-utils.js"; | ||
import { reportDiagnostic } from "./lib.js"; | ||
|
||
export function $onValidate(program: Program) { | ||
// Pass along any diagnostics that might be returned from the HTTP library | ||
const tcgcContext = createTCGCContext(program); | ||
const [_, diagnostics] = getAllModelsWithDiagnostics(tcgcContext); | ||
if (diagnostics.length > 0) { | ||
program.reportDiagnostics(diagnostics); | ||
const languageScopes = getDefinedLanguageScopes(program); | ||
for (const scope of languageScopes) { | ||
validateClientNamesPerNamespace(tcgcContext, scope, program.getGlobalNamespaceType()); | ||
} | ||
} | ||
|
||
function getDefinedLanguageScopes(program: Program): Set<string | typeof AllScopes> { | ||
const languageScopes = new Set<string | typeof AllScopes>(); | ||
for (const value of program.stateMap(clientNameKey).values()) { | ||
if (value[AllScopes]) { | ||
languageScopes.add(AllScopes); | ||
} | ||
for (const languageScope of Object.keys(value)) { | ||
languageScopes.add(languageScope); | ||
} | ||
} | ||
return languageScopes; | ||
} | ||
|
||
function validateClientNamesPerNamespace( | ||
tcgcContext: TCGCContext, | ||
scope: string | typeof AllScopes, | ||
namespace: Namespace | ||
) { | ||
// Check for duplicate client names for models, enums, and unions | ||
validateClientNamesCore(tcgcContext, scope, [ | ||
...namespace.models.values(), | ||
...namespace.enums.values(), | ||
...namespace.unions.values(), | ||
]); | ||
|
||
// Check for duplicate client names for operations | ||
validateClientNamesCore(tcgcContext, scope, namespace.operations.values()); | ||
|
||
// Check for duplicate client names for interfaces | ||
validateClientNamesCore(tcgcContext, scope, namespace.interfaces.values()); | ||
|
||
// Check for duplicate client names for scalars | ||
validateClientNamesCore(tcgcContext, scope, namespace.scalars.values()); | ||
|
||
// Check for duplicate client names for namespaces | ||
validateClientNamesCore(tcgcContext, scope, namespace.namespaces.values()); | ||
|
||
// Check for duplicate client names for model properties | ||
for (const model of namespace.models.values()) { | ||
validateClientNamesCore(tcgcContext, scope, model.properties.values()); | ||
} | ||
|
||
// Check for duplicate client names for enum members | ||
for (const item of namespace.enums.values()) { | ||
validateClientNamesCore(tcgcContext, scope, item.members.values()); | ||
} | ||
|
||
// Check for duplicate client names for union variants | ||
for (const item of namespace.unions.values()) { | ||
validateClientNamesCore(tcgcContext, scope, item.variants.values()); | ||
} | ||
|
||
// Check for duplicate client names for nested namespaces | ||
for (const item of namespace.namespaces.values()) { | ||
validateClientNamesPerNamespace(tcgcContext, scope, item); | ||
} | ||
} | ||
|
||
function validateClientNamesCore( | ||
tcgcContext: TCGCContext, | ||
scope: string | typeof AllScopes, | ||
items: Iterable< | ||
| Namespace | ||
| Scalar | ||
| Operation | ||
| Interface | ||
| Model | ||
| Enum | ||
| Union | ||
| ModelProperty | ||
| EnumMember | ||
| UnionVariant | ||
> | ||
) { | ||
const duplicateTracker = new DuplicateTracker< | ||
string, | ||
Type | DecoratorExpressionNode | AugmentDecoratorStatementNode | ||
>(); | ||
|
||
for (const item of items) { | ||
const clientName = getClientNameOverride(tcgcContext, item, scope); | ||
if (clientName !== undefined) { | ||
const clientNameDecorator = item.decorators.find((x) => x.definition?.name === "@clientName"); | ||
if (clientNameDecorator?.node !== undefined) { | ||
duplicateTracker.track(clientName, clientNameDecorator.node); | ||
} | ||
} else { | ||
if (item.name !== undefined && typeof item.name === "string") { | ||
duplicateTracker.track(item.name, item); | ||
} | ||
} | ||
} | ||
|
||
reportDuplicateClientNames(tcgcContext.program, duplicateTracker, scope); | ||
} | ||
|
||
function reportDuplicateClientNames( | ||
program: Program, | ||
duplicateTracker: DuplicateTracker< | ||
string, | ||
Type | DecoratorExpressionNode | AugmentDecoratorStatementNode | ||
>, | ||
scope: string | typeof AllScopes | ||
) { | ||
for (const [name, duplicates] of duplicateTracker.entries()) { | ||
for (const item of duplicates) { | ||
const scopeStr = scope === AllScopes ? "AllScopes" : scope; | ||
// If the item is a decorator application node | ||
if ( | ||
item.kind === SyntaxKind.DecoratorExpression || | ||
item.kind === SyntaxKind.AugmentDecoratorStatement | ||
) { | ||
reportDiagnostic(program, { | ||
code: "duplicate-client-name", | ||
format: { name, scope: scopeStr }, | ||
target: item, | ||
}); | ||
} else { | ||
reportDiagnostic(program, { | ||
code: "duplicate-client-name", | ||
messageId: "nonDecorator", | ||
format: { name, scope: scopeStr }, | ||
target: item, | ||
}); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.