diff --git a/common/changes/@typespec/compiler/move-list-decorator_2023-06-20-11-35.json b/common/changes/@typespec/compiler/move-list-decorator_2023-06-20-11-35.json new file mode 100644 index 00000000000..58db5af62a6 --- /dev/null +++ b/common/changes/@typespec/compiler/move-list-decorator_2023-06-20-11-35.json @@ -0,0 +1,20 @@ +{ + "changes": [ + { + "packageName": "@typespec/compiler", + "comment": "**Deprecate** `@list` decorator in favor of `@listsResource` in `@typespec/rest`", + "type": "none" + }, + { + "packageName": "@typespec/compiler", + "comment": "**Deprecate** `isListOperation` function in favor of `isListOperation` in `@typespec/rest`", + "type": "none" + }, + { + "packageName": "@typespec/compiler", + "comment": "**Deprecate** `getListOperationType` function", + "type": "none" + } + ], + "packageName": "@typespec/compiler" +} diff --git a/common/changes/@typespec/library-linter/move-list-decorator_2023-06-20-11-35.json b/common/changes/@typespec/library-linter/move-list-decorator_2023-06-20-11-35.json new file mode 100644 index 00000000000..f311c1375cd --- /dev/null +++ b/common/changes/@typespec/library-linter/move-list-decorator_2023-06-20-11-35.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/library-linter", + "comment": "", + "type": "none" + } + ], + "packageName": "@typespec/library-linter" +} \ No newline at end of file diff --git a/common/changes/@typespec/rest/move-list-decorator_2023-06-20-11-35.json b/common/changes/@typespec/rest/move-list-decorator_2023-06-20-11-35.json new file mode 100644 index 00000000000..b19e50bc5ad --- /dev/null +++ b/common/changes/@typespec/rest/move-list-decorator_2023-06-20-11-35.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/rest", + "comment": "Add `isListOperation` function migrated from `@typespec/compiler`", + "type": "none" + } + ], + "packageName": "@typespec/rest" +} \ No newline at end of file diff --git a/docs/standard-library/built-in-decorators.md b/docs/standard-library/built-in-decorators.md index e6e61a4dd7b..2d4b0f02fa2 100644 --- a/docs/standard-library/built-in-decorators.md +++ b/docs/standard-library/built-in-decorators.md @@ -305,6 +305,25 @@ Invalid, ``` +### `@list` {#@list} + +Mark this operation as a `list` operation for resource types. + +```typespec +@list(listedType?: Model) +``` + +#### Target + +`Operation` + +#### Parameters +| Name | Type | Description | +|------|------|-------------| +| listedType | `Model` | Optional type of the items in the list. | + + + ### `@maxItems` {#@maxItems} Specify the maximum number of items this array should have. diff --git a/packages/compiler/lib/decorators.tsp b/packages/compiler/lib/decorators.tsp index 13c53068aa6..260c2605356 100644 --- a/packages/compiler/lib/decorators.tsp +++ b/packages/compiler/lib/decorators.tsp @@ -234,6 +234,13 @@ extern dec maxValueExclusive(target: numeric | ModelProperty, value: valueof num */ extern dec secret(target: string | ModelProperty); +/** + * Mark this operation as a `list` operation for resource types. + * @deprecated Use the `listsResource` decorator in `@typespec/rest` instead. + * @param listedType Optional type of the items in the list. + */ +extern dec list(target: Operation, listedType?: Model); + /** * Attaches a tag to an operation, interface, or namespace. Multiple `@tag` decorators can be specified to attach multiple tags to a TypeSpec element. * @param tag Tag value diff --git a/packages/compiler/src/lib/decorators.ts b/packages/compiler/src/lib/decorators.ts index 9c341d2e9b0..c525ebee429 100644 --- a/packages/compiler/src/lib/decorators.ts +++ b/packages/compiler/src/lib/decorators.ts @@ -774,6 +774,9 @@ export function $withoutDefaultValues(context: DecoratorContext, target: Model) const listPropertiesKey = createStateSymbol("listProperties"); +/** + * @deprecated Use the `listsResource` decorator in `@typespec/rest` instead. + */ export function $list(context: DecoratorContext, target: Operation, listedType?: Type) { if (listedType && listedType.kind === "TemplateParameter") { // Silently return because this is probably being used in a templated interface @@ -790,10 +793,16 @@ export function $list(context: DecoratorContext, target: Operation, listedType?: context.program.stateMap(listPropertiesKey).set(target, listedType); } +/** + * @deprecated This function is unused and will be removed in a future release. + */ export function getListOperationType(program: Program, target: Type): Model | undefined { return program.stateMap(listPropertiesKey).get(target); } +/** + * @deprecated Use `isListOperation` in `@typespec/rest` instead. + */ export function isListOperation(program: Program, target: Operation): boolean { // The type stored for the operation return program.stateMap(listPropertiesKey).has(target); diff --git a/packages/library-linter/src/linter.ts b/packages/library-linter/src/linter.ts index c4fba622a18..069a29bcb00 100644 --- a/packages/library-linter/src/linter.ts +++ b/packages/library-linter/src/linter.ts @@ -41,12 +41,7 @@ function validateNoExportAtRoot(program: Program, root: Namespace) { } } -const excludeDecoratorSignature = new Set([ - "@docFromComment", - "@indexer", - "@test", - "@list", // TODO check if we actually need this one https://github.com/microsoft/typespec/issues/1978 -]); +const excludeDecoratorSignature = new Set(["@docFromComment", "@indexer", "@test"]); function validateDecoratorSignature(program: Program) { function navigate(sym: Sym) { if (sym.flags & SymbolFlags.Decorator) { diff --git a/packages/rest/src/rest.ts b/packages/rest/src/rest.ts index ed7a03e2189..a416c064419 100644 --- a/packages/rest/src/rest.ts +++ b/packages/rest/src/rest.ts @@ -1,5 +1,4 @@ import { - $list, createDiagnosticCollector, DecoratorContext, DiagnosticResult, @@ -412,15 +411,22 @@ export function $deletesResource( } export function $listsResource(context: DecoratorContext, entity: Operation, resourceType: Model) { - // Add the @list decorator too so that collection routes are generated correctly - context.call($list, entity, resourceType); - // Add path segment for resource type key context.call($segmentOf, entity, resourceType); setResourceOperation(context, entity, resourceType, "list"); } +/** + * Returns `true` if the given operation is marked as a list operation. + * @param program the TypeSpec program + * @param target the target operation + */ +export function isListOperation(program: Program, target: Operation): boolean { + // Is the given operation a `list` operation? + return getResourceOperation(program, target)?.operation === "list"; +} + function lowerCaseFirstChar(str: string): string { return str[0].toLocaleLowerCase() + str.substring(1); } diff --git a/packages/rest/test/routes.test.ts b/packages/rest/test/routes.test.ts index 72c57d96365..ec824c539ca 100644 --- a/packages/rest/test/routes.test.ts +++ b/packages/rest/test/routes.test.ts @@ -118,7 +118,7 @@ describe("rest: routes", () => { @autoRoute interface SubInterface { @get - @list(Subthing) + @listsResource(Subthing) @segmentOf(Subthing) GetSubthings(...KeysOf): string;