Feature: `valueof` for string, numeric and boolean literals (#1877)

This commit is contained in:
Timothee Guerin 2023-06-02 13:35:00 -07:00 коммит произвёл GitHub
Родитель d53a6f2b2a
Коммит 6733c5b0a8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
50 изменённых файлов: 739 добавлений и 268 удалений

Просмотреть файл

@ -0,0 +1,20 @@
{
"changes": [
{
"packageName": "@typespec/compiler",
"comment": "Added new keyword `valueof` designed to request for a value type in a decorator parameter.",
"type": "none"
},
{
"packageName": "@typespec/compiler",
"comment": "**BREAKING** Decorator API will not be marshalling values unless the parameter type is using `valueof`. `extern dec foo(target, value: string)` should be changed to `extern dec foo(target, value: valueof string)`.",
"type": "none"
},
{
"packageName": "@typespec/compiler",
"comment": "**DEPRECATION** To make transition to valueof smoother if using a template parameter inside a decorator that is now using valueof the existing parmater constraint will still be compatible but emit a warning.",
"type": "none"
}
],
"packageName": "@typespec/compiler"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/http",
"comment": "Update decorators to use `valueof`",
"type": "none"
}
],
"packageName": "@typespec/http"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/openapi",
"comment": "Update decorators to use `valueof`",
"type": "none"
}
],
"packageName": "@typespec/openapi"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/protobuf",
"comment": "Update decorators to use `valueof`",
"type": "minor"
}
],
"packageName": "@typespec/protobuf"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/rest",
"comment": "Update decorators to use `valueof`",
"type": "none"
}
],
"packageName": "@typespec/rest"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/versioning",
"comment": "Update decorators to use `valueof`",
"type": "none"
}
],
"packageName": "@typespec/versioning"
}

Просмотреть файл

@ -40,7 +40,7 @@ extern dec track(target: Model | Enum);
A decorator parameter can be marked optional using `?`
```typespec
extern dec track(target: Model | Enum, name?: StringLiteral);
extern dec track(target: Model | Enum, name?: valueof string);
```
### Rest parameters
@ -48,7 +48,29 @@ extern dec track(target: Model | Enum, name?: StringLiteral);
A decorator's last parameter can be prefixed with `...` to collect all the remaining arguments. The type of that parameter must be an `array expression`
```typespec
extern dec track(target: Model | Enum, ...names: StringLiteral[]);
extern dec track(target: Model | Enum, ...names: valueof string[]);
```
## Ask for a value type
It is common that decorators parameter will expect a value(e.g. a string or a number). However just using `: string` as the type will also allow a user of the decorator to pass `string` itself or a custom scalar extending string as well as union of strings.
Instead the decorator can use `valueof <T>` to specify that it is expecting a value of that kind.
| Example | Description |
| ----------------- | ---------------- |
| `valueof string` | Expect a string |
| `valueof float64` | Expect a float |
| `valueof int32` | Expect a number |
| `valueof boolean` | Expect a boolean |
```tsp
extern dec tag(target: unknown, value: valueof string);
// bad
@tag(string)
// good
@tag("This is the tag name")
```
## Implement the decorator in JS
@ -63,7 +85,7 @@ Decorators can be implemented in JavaScript by prefixing the function name with
// model.ts
import type { DecoratorContext, Type } from "@typespec/compiler";
export function $logType(context: DecoratorContext, target: Type, name: string) {
export function $logType(context: DecoratorContext, target: Type, name: valueof string) {
console.log(name + ": " + targetType.kind);
}
```
@ -92,13 +114,13 @@ model Dog {
### Decorator parameter marshalling
For certain TypeSpec types(Literal types) the decorator do not receive the actual type but a marshalled value. This is to simplify the most common cases.
For certain TypeSpec types(Literal types) the decorator do not receive the actual type but a marshalled value if the decorator parmaeter type is a `valueof`. This is to simplify the most common cases.
| TypeSpec Type | Marshalled value in JS |
| ---------------- | ---------------------- |
| `StringLiteral` | `string` |
| `NumericLiteral` | `number` |
| `BooleanLiteral` | `boolean` |
| TypeSpec Type | Marshalled value in JS |
| ----------------- | ---------------------- |
| `valueof string` | `string` |
| `valueof numeric` | `number` |
| `valueof boolean` | `boolean` |
for all the other types they are not transformed.

Просмотреть файл

@ -11,7 +11,7 @@ toc_max_heading_level: 3
Mark this type as deprecated
```typespec
@deprecated(message: string)
@deprecated(message: valueof string)
```
#### Target
@ -21,7 +21,7 @@ Mark this type as deprecated
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| message | `scalar string` | Deprecation message. |
| message | `valueof scalar string` | Deprecation message. |
#### Examples
@ -36,7 +36,7 @@ op Action<T>(): T;
Specify the property to be used to discriminate this type.
```typespec
@discriminator(propertyName: string)
@discriminator(propertyName: valueof string)
```
#### Target
@ -46,7 +46,7 @@ Specify the property to be used to discriminate this type.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| propertyName | `scalar string` | The property name to use for discrimination |
| propertyName | `valueof scalar string` | The property name to use for discrimination |
#### Examples
@ -72,7 +72,7 @@ model Dog extends Pet {kind: "dog", bark: boolean}
Attach a documentation string.
```typespec
@doc(doc: string, formatArgs?: {})
@doc(doc: valueof string, formatArgs?: {})
```
#### Target
@ -82,7 +82,7 @@ Attach a documentation string.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| doc | `scalar string` | Documentation string |
| doc | `valueof scalar string` | Documentation string |
| formatArgs | `model {}` | Record with key value pair that can be interpolated in the doc. |
#### Examples
@ -162,7 +162,7 @@ This differs from the `@pattern` decorator which is meant to specify a regular e
The format names are open ended and are left to emitter to interpret.
```typespec
@format(format: string)
@format(format: valueof string)
```
#### Target
@ -172,7 +172,7 @@ The format names are open ended and are left to emitter to interpret.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| format | `scalar string` | format name. |
| format | `valueof scalar string` | format name. |
#### Examples
@ -187,7 +187,7 @@ scalar uuid extends string;
Specifies how a templated type should name their instances.
```typespec
@friendlyName(name: string, formatArgs?: unknown)
@friendlyName(name: valueof string, formatArgs?: unknown)
```
#### Target
@ -197,7 +197,7 @@ Specifies how a templated type should name their instances.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| name | `scalar string` | name the template instance should take |
| name | `valueof scalar string` | name the template instance should take |
| formatArgs | `(intrinsic) unknown` | Model with key value used to interpolate the name |
#### Examples
@ -216,7 +216,7 @@ nextLink: string;
A debugging decorator used to inspect a type.
```typespec
@inspectType(text: string)
@inspectType(text: valueof string)
```
#### Target
@ -226,7 +226,7 @@ A debugging decorator used to inspect a type.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| text | `scalar string` | Custom text to log |
| text | `valueof scalar string` | Custom text to log |
@ -235,7 +235,7 @@ A debugging decorator used to inspect a type.
A debugging decorator used to inspect a type name.
```typespec
@inspectTypeName(text: string)
@inspectTypeName(text: valueof string)
```
#### Target
@ -245,7 +245,7 @@ A debugging decorator used to inspect a type name.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| text | `scalar string` | Custom text to log |
| text | `valueof scalar string` | Custom text to log |
@ -254,7 +254,7 @@ A debugging decorator used to inspect a type name.
Mark a model property as the key to identify instances of that type
```typespec
@key(altName?: string)
@key(altName?: valueof string)
```
#### Target
@ -264,7 +264,7 @@ Mark a model property as the key to identify instances of that type
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| altName | `scalar string` | Name of the property. If not specified, the decorated property name is used. |
| altName | `valueof scalar string` | Name of the property. If not specified, the decorated property name is used. |
#### Examples
@ -310,7 +310,7 @@ Invalid,
Specify the maximum number of items this array should have.
```typespec
@maxItems(value: integer)
@maxItems(value: valueof integer)
```
#### Target
@ -320,7 +320,7 @@ Specify the maximum number of items this array should have.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar integer` | Maximum number |
| value | `valueof scalar integer` | Maximum number |
#### Examples
@ -335,7 +335,7 @@ model Endpoints is string[];
Specify the maximum length this string type should be.
```typespec
@maxLength(value: integer)
@maxLength(value: valueof integer)
```
#### Target
@ -345,7 +345,7 @@ Specify the maximum length this string type should be.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar integer` | Maximum length |
| value | `valueof scalar integer` | Maximum length |
#### Examples
@ -360,7 +360,7 @@ scalar Username extends string;
Specify the maximum value this numeric type should be.
```typespec
@maxValue(value: numeric)
@maxValue(value: valueof numeric)
```
#### Target
@ -370,7 +370,7 @@ Specify the maximum value this numeric type should be.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar numeric` | Maximum value |
| value | `valueof scalar numeric` | Maximum value |
#### Examples
@ -386,7 +386,7 @@ Specify the maximum value this numeric type should be, exclusive of the given
value.
```typespec
@maxValueExclusive(value: numeric)
@maxValueExclusive(value: valueof numeric)
```
#### Target
@ -396,7 +396,7 @@ value.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar numeric` | Maximum value |
| value | `valueof scalar numeric` | Maximum value |
#### Examples
@ -411,7 +411,7 @@ scalar distance is float64;
Specify the minimum number of items this array should have.
```typespec
@minItems(value: integer)
@minItems(value: valueof integer)
```
#### Target
@ -421,7 +421,7 @@ Specify the minimum number of items this array should have.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar integer` | Minimum number |
| value | `valueof scalar integer` | Minimum number |
#### Examples
@ -436,7 +436,7 @@ model Endpoints is string[];
Specify the minimum length this string type should be.
```typespec
@minLength(value: integer)
@minLength(value: valueof integer)
```
#### Target
@ -446,7 +446,7 @@ Specify the minimum length this string type should be.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar integer` | Minimum length |
| value | `valueof scalar integer` | Minimum length |
#### Examples
@ -461,7 +461,7 @@ scalar Username extends string;
Specify the minimum value this numeric type should be.
```typespec
@minValue(value: numeric)
@minValue(value: valueof numeric)
```
#### Target
@ -471,7 +471,7 @@ Specify the minimum value this numeric type should be.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar numeric` | Minimum value |
| value | `valueof scalar numeric` | Minimum value |
#### Examples
@ -487,7 +487,7 @@ Specify the minimum value this numeric type should be, exclusive of the given
value.
```typespec
@minValueExclusive(value: numeric)
@minValueExclusive(value: valueof numeric)
```
#### Target
@ -497,7 +497,7 @@ value.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| value | `scalar numeric` | Minimum value |
| value | `valueof scalar numeric` | Minimum value |
#### Examples
@ -542,7 +542,7 @@ The following syntax is allowed: alternations (`|`), quantifiers (`?`, `*`, `+`,
Advanced features like look-around, capture groups, and references are not supported.
```typespec
@pattern(pattern: string)
@pattern(pattern: valueof string)
```
#### Target
@ -552,7 +552,7 @@ Advanced features like look-around, capture groups, and references are not suppo
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| pattern | `scalar string` | Regular expression. |
| pattern | `valueof scalar string` | Regular expression. |
#### Examples
@ -567,7 +567,7 @@ scalar LowerAlpha extends string;
Provide an alternative name for this type.
```typespec
@projectedName(targetName: string, projectedName: string)
@projectedName(targetName: valueof string, projectedName: valueof string)
```
#### Target
@ -577,8 +577,8 @@ Provide an alternative name for this type.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| targetName | `scalar string` | Projection target |
| projectedName | `scalar string` | Alternative name |
| targetName | `valueof scalar string` | Projection target |
| projectedName | `valueof scalar string` | Alternative name |
#### Examples
@ -657,7 +657,7 @@ namespace PetStore;
Typically a short, single-line description.
```typespec
@summary(summary: string)
@summary(summary: valueof string)
```
#### Target
@ -667,7 +667,7 @@ Typically a short, single-line description.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| summary | `scalar string` | Summary string. |
| summary | `valueof scalar string` | Summary string. |
#### Examples
@ -682,7 +682,7 @@ model Pet {}
Attaches a tag to an operation, interface, or namespace. Multiple `@tag` decorators can be specified to attach multiple tags to a TypeSpec element.
```typespec
@tag(tag: string)
@tag(tag: valueof string)
```
#### Target
@ -692,7 +692,7 @@ Attaches a tag to an operation, interface, or namespace. Multiple `@tag` decorat
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| tag | `scalar string` | Tag value |
| tag | `valueof scalar string` | Tag value |
@ -715,7 +715,7 @@ with standard emitters that interpret them as follows:
See also: [Automatic visibility](https://microsoft.github.io/typespec/standard-library/http/operations#automatic-visibility)
```typespec
@visibility(...visibilities: string[])
@visibility(...visibilities: valueof string[])
```
#### Target
@ -725,7 +725,7 @@ See also: [Automatic visibility](https://microsoft.github.io/typespec/standard-l
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| visibilities | `model string[]` | List of visibilities which apply to this property. |
| visibilities | `valueof model string[]` | List of visibilities which apply to this property. |
#### Examples
@ -746,7 +746,7 @@ name: string;
Set the visibility of key properties in a model if not already set.
```typespec
@withDefaultKeyVisibility(visibility: unknown)
@withDefaultKeyVisibility(visibility: valueof string)
```
#### Target
@ -756,7 +756,7 @@ Set the visibility of key properties in a model if not already set.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| visibility | `(intrinsic) unknown` | The desired default visibility value. If a key property already has a `visibility` decorator then the default visibility is not applied. |
| visibility | `valueof scalar string` | The desired default visibility value. If a key property already has a `visibility` decorator then the default visibility is not applied. |
@ -843,7 +843,7 @@ When using an emitter that applies visibility automatically, it is generally
not necessary to use this decorator.
```typespec
@withVisibility(...visibilities: string[])
@withVisibility(...visibilities: valueof string[])
```
#### Target
@ -853,7 +853,7 @@ not necessary to use this decorator.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| visibilities | `model string[]` | List of visibilities which apply to this property. |
| visibilities | `valueof model string[]` | List of visibilities which apply to this property. |
#### Examples

Просмотреть файл

@ -127,7 +127,7 @@ op create(@header({name: "X-Color", format: "csv"}) colors: string[]): void;
Specify if inapplicable metadata should be included in the payload for the given entity.
```typespec
@TypeSpec.Http.includeInapplicableMetadataInPayload(value: boolean)
@TypeSpec.Http.includeInapplicableMetadataInPayload(value: valueof boolean)
```
#### Target
@ -136,9 +136,9 @@ Specify if inapplicable metadata should be included in the payload for the given
#### Parameters
| Name | Type | Description |
| ----- | ---------------- | --------------------------------------------------------------- |
| value | `scalar boolean` | If true, inapplicable metadata will be included in the payload. |
| Name | Type | Description |
| ----- | ------------------------ | --------------------------------------------------------------- |
| value | `valueof scalar boolean` | If true, inapplicable metadata will be included in the payload. |
### `@patch` {#@TypeSpec.Http.patch}
@ -167,7 +167,7 @@ None
Explicitly specify that this property is to be interpolated as a path parameter.
```typespec
@TypeSpec.Http.path(paramName?: string)
@TypeSpec.Http.path(paramName?: valueof string)
```
#### Target
@ -176,9 +176,9 @@ Explicitly specify that this property is to be interpolated as a path parameter.
#### Parameters
| Name | Type | Description |
| --------- | --------------- | --------------------------------------------------- |
| paramName | `scalar string` | Optional name of the parameter in the url template. |
| Name | Type | Description |
| --------- | ----------------------- | --------------------------------------------------- |
| paramName | `valueof scalar string` | Optional name of the parameter in the url template. |
#### Examples
@ -267,7 +267,7 @@ it will be used as a prefix to the route URI of the operation.
`@route` can only be applied to operations, namespaces, and interfaces.
```typespec
@TypeSpec.Http.route(path: string, options?: (anonymous model))
@TypeSpec.Http.route(path: valueof string, options?: (anonymous model))
```
#### Target
@ -278,7 +278,7 @@ it will be used as a prefix to the route URI of the operation.
| Name | Type | Description |
| ------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| path | `scalar string` | Relative route path. Cannot include query parameters. |
| path | `valueof scalar string` | Relative route path. Cannot include query parameters. |
| options | `model (anonymous model)` | Set of parameters used to configure the route. Supports `{shared: true}` which indicates that the route may be shared by several operations. |
#### Examples
@ -293,7 +293,7 @@ op getWidget(@path id: string): Widget;
Specify the endpoint for this service.
```typespec
@TypeSpec.Http.server(url: string, description: string, parameters?: Record<unknown>)
@TypeSpec.Http.server(url: valueof string, description: valueof string, parameters?: Record<unknown>)
```
#### Target
@ -304,8 +304,8 @@ Specify the endpoint for this service.
| Name | Type | Description |
| ----------- | ----------------------- | ------------------------------------------------------- |
| url | `scalar string` | Server endpoint |
| description | `scalar string` | Description of the endpoint |
| url | `valueof scalar string` | Server endpoint |
| description | `valueof scalar string` | Description of the endpoint |
| parameters | `model Record<unknown>` | Optional set of parameters used to interpolate the url. |
#### Examples

Просмотреть файл

@ -39,7 +39,7 @@ op listPets(): Pet[] | PetStoreResponse;
Attach some custom data to the OpenAPI element generated from this type.
```typespec
@OpenAPI.extension(key: string, value: unknown)
@OpenAPI.extension(key: valueof string, value: unknown)
```
#### Target
@ -48,10 +48,10 @@ Attach some custom data to the OpenAPI element generated from this type.
#### Parameters
| Name | Type | Description |
| ----- | --------------------- | ----------------------------------- |
| key | `scalar string` | Extension key. Must start with `x-` |
| value | `(intrinsic) unknown` | Extension value. |
| Name | Type | Description |
| ----- | ----------------------- | ----------------------------------- |
| key | `valueof scalar string` | Extension key. Must start with `x-` |
| value | `(intrinsic) unknown` | Extension value. |
#### Examples
@ -66,7 +66,7 @@ op read(): string;
Specify the OpenAPI `externalDocs` property for this type.
```typespec
@OpenAPI.externalDocs(url: string, description?: string)
@OpenAPI.externalDocs(url: valueof string, description?: valueof string)
```
#### Target
@ -75,10 +75,10 @@ Specify the OpenAPI `externalDocs` property for this type.
#### Parameters
| Name | Type | Description |
| ----------- | --------------- | ----------------------- |
| url | `scalar string` | Url to the docs |
| description | `scalar string` | Description of the docs |
| Name | Type | Description |
| ----------- | ----------------------- | ----------------------- |
| url | `valueof scalar string` | Url to the docs |
| description | `valueof scalar string` | Description of the docs |
#### Examples
@ -92,7 +92,7 @@ op listPets(): Pet[];
Specify the OpenAPI `operationId` property for this operation.
```typespec
@OpenAPI.operationId(operationId: string)
@OpenAPI.operationId(operationId: valueof string)
```
#### Target
@ -101,9 +101,9 @@ Specify the OpenAPI `operationId` property for this operation.
#### Parameters
| Name | Type | Description |
| ----------- | --------------- | ------------------- |
| operationId | `scalar string` | Operation id value. |
| Name | Type | Description |
| ----------- | ----------------------- | ------------------- |
| operationId | `valueof scalar string` | Operation id value. |
#### Examples

Просмотреть файл

@ -20,7 +20,7 @@ The field index of a Protobuf message must:
- not fall within any range that was [marked reserved](#
```typespec
@TypeSpec.Protobuf.field(index: uint32)
@TypeSpec.Protobuf.field(index: valueof uint32)
```
#### Target
@ -29,9 +29,9 @@ The field index of a Protobuf message must:
#### Parameters
| Name | Type | Description |
| ----- | --------------- | ------------------------------------ |
| index | `scalar uint32` | The whole-number index of the field. |
| Name | Type | Description |
| ----- | ----------------------- | ------------------------------------ |
| index | `valueof scalar uint32` | The whole-number index of the field. |
#### Examples
@ -108,7 +108,7 @@ See _[Protobuf Language Guide - Reserved Fields](https://protobuf.dev/programmin
information.
```typespec
@TypeSpec.Protobuf.reserve(...reservations: string | [uint32, uint32] | uint32[])
@TypeSpec.Protobuf.reserve(...reservations: valueof string | [uint32, uint32] | uint32[])
```
#### Target
@ -117,9 +117,9 @@ information.
#### Parameters
| Name | Type | Description |
| ------------ | ---------------------------------------------- | ---------------------------- |
| reservations | `model string \| [uint32, uint32] \| uint32[]` | a list of field reservations |
| Name | Type | Description |
| ------------ | ------------------------------------------------------ | ---------------------------- |
| reservations | `valueof model string \| [uint32, uint32] \| uint32[]` | a list of field reservations |
#### Examples

Просмотреть файл

@ -13,7 +13,7 @@ toc_max_heading_level: 3
Specify this operation is an action. (Scoped to a resource item /pets/{petId}/my-action)
```typespec
@TypeSpec.Rest.action(name?: string)
@TypeSpec.Rest.action(name?: valueof string)
```
#### Target
@ -22,16 +22,16 @@ Specify this operation is an action. (Scoped to a resource item /pets/{petId}/my
#### Parameters
| Name | Type | Description |
| ---- | --------------- | ----------------------------------------------------------------------------- |
| name | `scalar string` | Name of the action. If not specified, the name of the operation will be used. |
| Name | Type | Description |
| ---- | ----------------------- | ----------------------------------------------------------------------------- |
| name | `valueof scalar string` | Name of the action. If not specified, the name of the operation will be used. |
### `@actionSeparator` {#@TypeSpec.Rest.actionSeparator}
Defines the separator string that is inserted before the action name in auto-generated routes for actions.
```typespec
@TypeSpec.Rest.actionSeparator(seperator: / | : | /:)
@TypeSpec.Rest.actionSeparator(seperator: valueof / | : | /:)
```
#### Target
@ -40,9 +40,9 @@ Defines the separator string that is inserted before the action name in auto-gen
#### Parameters
| Name | Type | Description |
| --------- | -------------------- | ---------------------------------------------------------------- |
| seperator | `union / \| : \| /:` | Seperator seperating the action segment from the rest of the url |
| Name | Type | Description |
| --------- | ---------------------------- | ---------------------------------------------------------------- |
| seperator | `valueof union / \| : \| /:` | Seperator seperating the action segment from the rest of the url |
### `@autoRoute` {#@TypeSpec.Rest.autoRoute}
@ -74,7 +74,7 @@ get(@segment("pets") @path id: string): void; //-> route: /pets/{id}
Specify this operation is a collection action. (Scopped to a resource, /pets/my-action)
```typespec
@TypeSpec.Rest.collectionAction(resourceType: Model, name?: string)
@TypeSpec.Rest.collectionAction(resourceType: Model, name?: valueof string)
```
#### Target
@ -83,17 +83,17 @@ Specify this operation is a collection action. (Scopped to a resource, /pets/my-
#### Parameters
| Name | Type | Description |
| ------------ | --------------- | ----------------------------------------------------------------------------- |
| resourceType | `Model` | Resource marked with |
| name | `scalar string` | Name of the action. If not specified, the name of the operation will be used. |
| Name | Type | Description |
| ------------ | ----------------------- | ----------------------------------------------------------------------------- |
| resourceType | `Model` | Resource marked with |
| name | `valueof scalar string` | Name of the action. If not specified, the name of the operation will be used. |
### `@copyResourceKeyParameters` {#@TypeSpec.Rest.copyResourceKeyParameters}
Copy the resource key parameters on the model
```typespec
@TypeSpec.Rest.copyResourceKeyParameters(filter?: string)
@TypeSpec.Rest.copyResourceKeyParameters(filter?: valueof string)
```
#### Target
@ -102,9 +102,9 @@ Copy the resource key parameters on the model
#### Parameters
| Name | Type | Description |
| ------ | --------------- | ------------------------------------- |
| filter | `scalar string` | Filter to exclude certain properties. |
| Name | Type | Description |
| ------ | ----------------------- | ------------------------------------- |
| filter | `valueof scalar string` | Filter to exclude certain properties. |
### `@createsOrReplacesResource` {#@TypeSpec.Rest.createsOrReplacesResource}
@ -237,7 +237,7 @@ Specify that this is a Read operation for a given resource.
Mark this model as a resource type with a name.
```typespec
@TypeSpec.Rest.resource(collectionName: string)
@TypeSpec.Rest.resource(collectionName: valueof string)
```
#### Target
@ -246,16 +246,16 @@ Mark this model as a resource type with a name.
#### Parameters
| Name | Type | Description |
| -------------- | --------------- | ---------------------- |
| collectionName | `scalar string` | type's collection name |
| Name | Type | Description |
| -------------- | ----------------------- | ---------------------- |
| collectionName | `valueof scalar string` | type's collection name |
### `@segment` {#@TypeSpec.Rest.segment}
Defines the preceding path segment for a
```typespec
@TypeSpec.Rest.segment(name: string)
@TypeSpec.Rest.segment(name: valueof string)
```
#### Target
@ -264,9 +264,9 @@ Defines the preceding path segment for a
#### Parameters
| Name | Type | Description |
| ---- | --------------- | ---------------------------------------------------------------------------------------------- |
| name | `scalar string` | Segment that will be inserted into the operation route before the path parameter's name field. |
| Name | Type | Description |
| ---- | ----------------------- | ---------------------------------------------------------------------------------------------- |
| name | `valueof scalar string` | Segment that will be inserted into the operation route before the path parameter's name field. |
#### Examples

Просмотреть файл

@ -67,7 +67,7 @@ Identifies when the target was removed.
Identifies when the target has been renamed.
```typespec
@TypeSpec.Versioning.renamedFrom(version: EnumMember, oldName: string)
@TypeSpec.Versioning.renamedFrom(version: EnumMember, oldName: valueof string)
```
#### Target
@ -76,10 +76,10 @@ Identifies when the target has been renamed.
#### Parameters
| Name | Type | Description |
| ------- | --------------- | ------------------------------------------- |
| version | `EnumMember` | The version that the target was renamed in. |
| oldName | `scalar string` | The previous name of the target. |
| Name | Type | Description |
| ------- | ----------------------- | ------------------------------------------- |
| version | `EnumMember` | The version that the target was renamed in. |
| oldName | `valueof scalar string` | The previous name of the target. |
### `@returnTypeChangedFrom` {#@TypeSpec.Versioning.returnTypeChangedFrom}

Просмотреть файл

@ -1,6 +1,6 @@
import { $docFromComment, getDeprecated, getIndexer } from "../lib/decorators.js";
import { createSymbol, createSymbolTable } from "./binder.js";
import { ProjectionError, compilerAssert } from "./diagnostics.js";
import { ProjectionError, compilerAssert, reportDeprecated } from "./diagnostics.js";
import { validateInheritanceDiscriminatedUnions } from "./helpers/discriminator-utils.js";
import { TypeNameOptions, getNamespaceFullName, getTypeName } from "./helpers/index.js";
import { createDiagnostic } from "./messages.js";
@ -124,6 +124,8 @@ import {
UnionVariant,
UnionVariantNode,
UnknownType,
ValueOfExpressionNode,
ValueType,
VoidType,
} from "./types.js";
import { MultiKeyMap, Mutable, createRekeyableMap, isArray, mutate } from "./util.js";
@ -192,10 +194,10 @@ export interface Checker {
* @returns [related, list of diagnostics]
*/
isTypeAssignableTo(
source: Type,
target: Type,
source: Type | ValueType,
target: Type | ValueType,
diagnosticTarget: DiagnosticTarget
): [boolean, Diagnostic[]];
): [boolean, readonly Diagnostic[]];
/**
* Check if the given type is one of the built-in standard TypeSpec Types.
@ -734,7 +736,7 @@ export function createChecker(program: Program): Checker {
});
if (node.constraint) {
type.constraint = getTypeForNode(node.constraint);
type.constraint = getTypeOrValueTypeForNode(node.constraint);
}
if (node.default) {
type.default = checkTemplateParameterDefault(
@ -768,7 +770,7 @@ export function createChecker(program: Program): Checker {
nodeDefault: Expression,
templateParameters: readonly TemplateParameterDeclarationNode[],
index: number,
constraint: Type | undefined
constraint: Type | ValueType | undefined
) {
function visit(node: Node) {
const type = getTypeForNode(node);
@ -895,7 +897,9 @@ export function createChecker(program: Program): Checker {
let [valueNode, value] = args[i];
if (declaredType.constraint) {
if (!checkTypeAssignable(value, declaredType.constraint, valueNode)) {
value = declaredType.constraint;
// TODO-TIM check if we expose this below
value =
declaredType.constraint?.kind === "Value" ? unknownType : declaredType.constraint;
}
}
values.push(value);
@ -906,7 +910,12 @@ export function createChecker(program: Program): Checker {
values.push(defaultValue);
} else {
tooFew = true;
values.push(declaredType.constraint ?? unknownType);
values.push(
// TODO-TIM check if we expose this below
declaredType.constraint?.kind === "Value"
? unknownType
: declaredType.constraint ?? unknownType
);
}
}
}
@ -1205,6 +1214,17 @@ export function createChecker(program: Program): Checker {
return unionType;
}
function checkValueOfExpression(
node: ValueOfExpressionNode,
mapper: TypeMapper | undefined
): ValueType {
const target = getTypeForNode(node.target, mapper);
return {
kind: "Value",
target,
};
}
/**
* Intersection produces a model type from the properties of its operands.
* So this doesn't work if we don't have a known set of properties (e.g.
@ -1313,12 +1333,20 @@ export function createChecker(program: Program): Checker {
if (links.declaredType) {
return links.declaredType as FunctionParameter;
}
if (node.rest && node.type && node.type.kind !== SyntaxKind.ArrayExpression) {
if (
node.rest &&
node.type &&
!(
node.type.kind === SyntaxKind.ArrayExpression ||
(node.type.kind === SyntaxKind.ValueOfExpression &&
node.type.target.kind === SyntaxKind.ArrayExpression)
)
) {
reportCheckerDiagnostic(
createDiagnostic({ code: "rest-parameter-array", target: node.type })
);
}
const type = node.type ? getTypeForNode(node.type) : unknownType;
const type = node.type ? getTypeOrValueTypeForNode(node.type) : unknownType;
const parameterType: FunctionParameter = createType({
kind: "FunctionParameter",
@ -1334,6 +1362,13 @@ export function createChecker(program: Program): Checker {
return parameterType;
}
function getTypeOrValueTypeForNode(node: Node, mapper?: TypeMapper) {
if (node.kind === SyntaxKind.ValueOfExpression) {
return checkValueOfExpression(node, mapper);
}
return getTypeForNode(node, mapper);
}
function mergeModelTypes(
node:
| ModelStatementNode
@ -2951,7 +2986,7 @@ export function createChecker(program: Program): Checker {
if (doc) {
type.decorators.unshift({
decorator: $docFromComment,
args: [{ value: createLiteralType(doc) }],
args: [{ value: createLiteralType(doc), jsValue: doc }],
});
}
}
@ -3018,7 +3053,7 @@ export function createChecker(program: Program): Checker {
const symbolLinks = getSymbolLinks(sym);
const args = checkDecoratorArguments(decNode, mapper);
let args = checkDecoratorArguments(decNode, mapper);
let hasError = false;
if (symbolLinks.declaredType === undefined) {
const decoratorDeclNode: DecoratorDeclarationStatementNode | undefined =
@ -3036,12 +3071,13 @@ export function createChecker(program: Program): Checker {
"Expected to find a decorator type."
);
// Means we have a decorator declaration.
hasError = checkDecoratorUsage(targetType, symbolLinks.declaredType, args, decNode);
[hasError, args] = checkDecoratorUsage(targetType, symbolLinks.declaredType, args, decNode);
}
if (hasError) {
return undefined;
}
return {
definition: symbolLinks.declaredType,
decorator: sym.value ?? ((...args: any[]) => {}),
node: decNode,
args,
@ -3053,7 +3089,7 @@ export function createChecker(program: Program): Checker {
declaration: Decorator,
args: DecoratorArgument[],
decoratorNode: Node
): boolean {
): [boolean, DecoratorArgument[]] {
let hasError = false;
const [targetValid] = isTypeAssignableTo(targetType, declaration.target.type, decoratorNode);
if (!targetValid) {
@ -3097,14 +3133,21 @@ export function createChecker(program: Program): Checker {
);
}
}
const resolvedArgs: DecoratorArgument[] = [];
for (const [index, parameter] of declaration.parameters.entries()) {
if (parameter.rest) {
const restType =
parameter.type.kind === "Model" ? parameter.type.indexer?.value : undefined;
const restType = getIndexType(
parameter.type.kind === "Value" ? parameter.type.target : parameter.type
);
if (restType) {
for (let i = index; i < args.length; i++) {
const arg = args[i];
if (arg && arg.value) {
resolvedArgs.push({
...arg,
jsValue: resolveDecoratorArgJsValue(arg.value, parameter.type.kind === "Value"),
});
if (!checkArgumentAssignable(arg.value, restType, arg.node!)) {
hasError = true;
}
@ -3115,17 +3158,34 @@ export function createChecker(program: Program): Checker {
}
const arg = args[index];
if (arg && arg.value) {
resolvedArgs.push({
...arg,
jsValue: resolveDecoratorArgJsValue(arg.value, parameter.type.kind === "Value"),
});
if (!checkArgumentAssignable(arg.value, parameter.type, arg.node!)) {
hasError = true;
}
}
}
return hasError;
return [hasError, resolvedArgs];
}
function getIndexType(type: Type): Type | undefined {
return type.kind === "Model" ? type.indexer?.value : undefined;
}
function resolveDecoratorArgJsValue(value: Type, valueOf: boolean) {
if (valueOf) {
if (value.kind === "Boolean" || value.kind === "String" || value.kind === "Number") {
return literalTypeToValue(value);
}
}
return value;
}
function checkArgumentAssignable(
argumentType: Type,
parameterType: Type,
parameterType: Type | ValueType,
diagnosticTarget: DiagnosticTarget
): boolean {
const [valid] = isTypeAssignableTo(argumentType, parameterType, diagnosticTarget);
@ -3174,6 +3234,7 @@ export function createChecker(program: Program): Checker {
const type = getTypeForNode(argNode, mapper);
return {
value: type,
jsValue: type,
node: argNode,
};
});
@ -4618,8 +4679,8 @@ export function createChecker(program: Program): Checker {
* @param diagnosticTarget Target for the diagnostic, unless something better can be inferred.
*/
function checkTypeAssignable(
source: Type,
target: Type,
source: Type | ValueType,
target: Type | ValueType,
diagnosticTarget: DiagnosticTarget
): boolean {
const [related, diagnostics] = isTypeAssignableTo(source, target, diagnosticTarget);
@ -4636,15 +4697,37 @@ export function createChecker(program: Program): Checker {
* @param diagnosticTarget Target for the diagnostic, unless something better can be inferred.
*/
function isTypeAssignableTo(
source: Type,
target: Type,
source: Type | ValueType,
target: Type | ValueType,
diagnosticTarget: DiagnosticTarget
): [boolean, Diagnostic[]] {
): [boolean, readonly Diagnostic[]] {
// BACKCOMPAT: Added May 2023 sprint, to be removed by June 2023 sprint
if (source.kind === "TemplateParameter" && source.constraint && target.kind === "Value") {
const [assignable] = isTypeAssignableTo(source.constraint, target.target, diagnosticTarget);
if (assignable) {
const constraint = getTypeName(source.constraint);
reportDeprecated(
program,
`Template constrainted to '${constraint}' will not be assignable to '${getTypeName(
target
)}' in the future. Update the constraint to be 'valueof ${constraint}'`,
diagnosticTarget
);
return [true, []];
}
}
if (source.kind === "TemplateParameter") {
source = source.constraint ?? unknownType;
}
if (source === target) return [true, []];
if (target.kind === "Value") {
return isAssignableToValueType(source, target, diagnosticTarget);
}
if (source.kind === "Value") {
return [false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
}
const isSimpleTypeRelated = isSimpleTypeAssignableTo(source, target);
if (isSimpleTypeRelated === true) {
return [true, []];
@ -4710,6 +4793,25 @@ export function createChecker(program: Program): Checker {
return [false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
}
function isAssignableToValueType(
source: Type | ValueType,
target: ValueType,
diagnosticTarget: DiagnosticTarget
): [boolean, readonly Diagnostic[]] {
if (source.kind === "Value") {
return isTypeAssignableTo(source.target, target.target, diagnosticTarget);
}
const [assignable, diagnostics] = isTypeAssignableTo(source, target.target, diagnosticTarget);
if (!assignable) {
return [assignable, diagnostics];
}
if (!isValueType(source)) {
return [false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
}
return [true, []];
}
function isReflectionType(type: Type): type is Model & { name: ReflectionTypeName } {
return (
type.kind === "Model" &&
@ -4846,7 +4948,7 @@ export function createChecker(program: Program): Checker {
source: Model,
target: Model & { indexer: ModelIndexer },
diagnosticTarget: DiagnosticTarget
): [boolean, Diagnostic[]] {
): [boolean, readonly Diagnostic[]] {
// Model expressions should be able to be assigned.
if (source.name === "" && target.indexer.key.name !== "integer") {
return isIndexConstraintValid(target.indexer.value, source, diagnosticTarget);
@ -4878,7 +4980,7 @@ export function createChecker(program: Program): Checker {
constraintType: Type,
type: Model,
diagnosticTarget: DiagnosticTarget
): [boolean, Diagnostic[]] {
): [boolean, readonly Diagnostic[]] {
for (const prop of type.properties.values()) {
const [related, diagnostics] = isTypeAssignableTo(
prop.type,
@ -4907,7 +5009,7 @@ export function createChecker(program: Program): Checker {
source: Tuple,
target: Tuple,
diagnosticTarget: DiagnosticTarget
): [boolean, Diagnostic[]] {
): [boolean, readonly Diagnostic[]] {
if (source.values.length !== target.values.length) {
return [
false,
@ -4973,8 +5075,8 @@ export function createChecker(program: Program): Checker {
}
function createUnassignableDiagnostic(
source: Type,
target: Type,
source: Type | ValueType,
target: Type | ValueType,
diagnosticTarget: DiagnosticTarget
) {
return createDiagnostic({
@ -5347,7 +5449,7 @@ function finishTypeForProgramAndChecker<T extends Type>(
if (docComment) {
typeDef.decorators.unshift({
decorator: $docFromComment,
args: [{ value: program.checker.createLiteralType(docComment) }],
args: [{ value: program.checker.createLiteralType(docComment), jsValue: docComment }],
});
}
for (const decApp of typeDef.decorators) {
@ -5372,7 +5474,7 @@ function applyDecoratorToType(program: Program, decApp: DecoratorApplication, ta
// peel `fn` off to avoid setting `this`.
try {
const args = marshalArgumentsForJS(decApp.args.map((x) => x.value));
const args = decApp.args.map((x) => x.jsValue);
const fn = decApp.decorator;
const context = createDecoratorContext(program, decApp);
fn(context, target, ...args);

Просмотреть файл

@ -10,6 +10,7 @@ import {
Operation,
Scalar,
Type,
ValueType,
} from "../types.js";
export interface TypeNameOptions {
@ -17,7 +18,7 @@ export interface TypeNameOptions {
printable?: boolean;
}
export function getTypeName(type: Type, options?: TypeNameOptions): string {
export function getTypeName(type: Type | ValueType, options?: TypeNameOptions): string {
switch (type.kind) {
case "Namespace":
return getNamespaceFullName(type, options);
@ -51,6 +52,8 @@ export function getTypeName(type: Type, options?: TypeNameOptions): string {
return type.value.toString();
case "Intrinsic":
return type.name;
case "Value":
return `valueof ${getTypeName(type.target, options)}`;
}
return "(unnamed type)";

Просмотреть файл

@ -97,6 +97,7 @@ import {
UnionStatementNode,
UnionVariantNode,
UsingStatementNode,
ValueOfExpressionNode,
VoidKeywordNode,
} from "./types.js";
import { isArray, mutate } from "./util.js";
@ -1050,6 +1051,18 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
}
return expr;
}
function parseValueOfExpression(): ValueOfExpressionNode {
const pos = tokenPos();
parseExpected(Token.ValueOfKeyword);
const target = parseExpression();
return {
kind: SyntaxKind.ValueOfExpression,
target,
...finishNode(pos),
};
}
function parseReferenceExpression(
message?: keyof CompilerDiagnostics["token-expected"]
): TypeReferenceNode {
@ -1242,6 +1255,8 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
function parsePrimaryExpression(): Expression {
while (true) {
switch (token()) {
case Token.ValueOfKeyword:
return parseValueOfExpression();
case Token.Identifier:
return parseReferenceExpression();
case Token.StringLiteral:
@ -2957,6 +2972,8 @@ export function visitChildren<T>(node: Node, cb: NodeCallback<T>): T | undefined
return visitNode(cb, node.id) || visitNode(cb, node.type);
case SyntaxKind.TypeReference:
return visitNode(cb, node.target) || visitEach(cb, node.arguments);
case SyntaxKind.ValueOfExpression:
return visitNode(cb, node.target);
case SyntaxKind.TupleExpression:
return visitEach(cb, node.values);
case SyntaxKind.UnionExpression:

Просмотреть файл

@ -553,14 +553,8 @@ export function createProjector(
for (const dec of decs) {
const args: DecoratorArgument[] = [];
for (const arg of dec.args) {
// filter out primitive arguments
if (typeof arg.value !== "object") {
args.push(arg);
continue;
}
const projected = projectType(arg.value);
args.push({ ...arg, value: projected });
const jsValue = typeof arg.jsValue === "object" ? projectType(arg.jsValue) : arg.jsValue;
args.push({ ...arg, value: projectType(arg.value), jsValue });
}
decorators.push({ ...dec, args });

Просмотреть файл

@ -118,6 +118,7 @@ export enum Token {
IfKeyword,
DecKeyword,
FnKeyword,
ValueOfKeyword,
// Add new statement keyword above
/** @internal */ __EndStatementKeyword,
@ -228,6 +229,7 @@ export const TokenDisplay = getTokenDisplayTable([
[Token.IfKeyword, "'if'"],
[Token.DecKeyword, "'dec'"],
[Token.FnKeyword, "'fn'"],
[Token.ValueOfKeyword, "'valueof'"],
[Token.ExtendsKeyword, "'extends'"],
[Token.TrueKeyword, "'true'"],
[Token.FalseKeyword, "'false'"],
@ -257,6 +259,7 @@ export const Keywords: ReadonlyMap<string, Token> = new Map([
["alias", Token.AliasKeyword],
["dec", Token.DecKeyword],
["fn", Token.FnKeyword],
["valueof", Token.ValueOfKeyword],
["true", Token.TrueKeyword],
["false", Token.FalseKeyword],
["return", Token.ReturnKeyword],

Просмотреть файл

@ -18,10 +18,16 @@ export type DecoratorArgumentValue = Type | number | string | boolean;
export interface DecoratorArgument {
value: Type;
/**
* Marshalled value for use in Javascript.
*/
jsValue: Type | string | number | boolean;
node?: Node;
}
export interface DecoratorApplication {
definition?: Decorator;
// TODO-TIM deprecate replace with `implementation`?
decorator: DecoratorFunction;
args: DecoratorArgument[];
node?: DecoratorExpressionNode | AugmentDecoratorStatementNode;
@ -137,6 +143,11 @@ export interface Projector {
projectedGlobalNamespace?: Namespace;
}
export interface ValueType {
kind: "Value"; // Todo remove?
target: Type;
}
export interface IntrinsicType extends BaseType {
kind: "Intrinsic";
name: "ErrorType" | "void" | "never" | "unknown" | "null";
@ -508,7 +519,7 @@ export interface UnionVariant extends BaseType, DecoratedType {
export interface TemplateParameter extends BaseType {
kind: "TemplateParameter";
node: TemplateParameterDeclarationNode;
constraint?: Type;
constraint?: Type | ValueType;
default?: Type;
}
@ -536,7 +547,7 @@ export interface FunctionParameter extends BaseType {
kind: "FunctionParameter";
node: FunctionParameterNode;
name: string;
type: Type;
type: Type | ValueType;
optional: boolean;
rest: boolean;
}
@ -723,6 +734,7 @@ export enum SyntaxKind {
VoidKeyword,
NeverKeyword,
UnknownKeyword,
ValueOfExpression,
TypeReference,
ProjectionReference,
TemplateParameterDeclaration,
@ -1012,6 +1024,7 @@ export type Expression =
| UnionExpressionNode
| IntersectionExpressionNode
| TypeReferenceNode
| ValueOfExpressionNode
| IdentifierNode
| StringLiteralNode
| NumericLiteralNode
@ -1246,6 +1259,11 @@ export interface IntersectionExpressionNode extends BaseNode {
readonly options: readonly Expression[];
}
export interface ValueOfExpressionNode extends BaseNode {
readonly kind: SyntaxKind.ValueOfExpression;
readonly target: Expression;
}
export interface TypeReferenceNode extends BaseNode {
readonly kind: SyntaxKind.TypeReference;
readonly target: MemberExpressionNode | IdentifierNode;
@ -1276,7 +1294,7 @@ export type Modifier = ExternKeywordNode;
* Represent a decorator declaration
* @example
* ```typespec
* extern dec doc(target: Type, value: StringLiteral);
* extern dec doc(target: Type, value: valueof string);
* ```
*/
export interface DecoratorDeclarationStatementNode extends BaseNode, DeclarationNode {

Просмотреть файл

@ -65,6 +65,7 @@ import {
UnionExpressionNode,
UnionStatementNode,
UnionVariantNode,
ValueOfExpressionNode,
} from "../../core/types.js";
import { isArray } from "../../core/util.js";
import { commentHandler } from "./comment-handler.js";
@ -193,6 +194,8 @@ export function printNode(
return printUnionVariant(path as AstPath<UnionVariantNode>, options, print);
case SyntaxKind.TypeReference:
return printTypeReference(path as AstPath<TypeReferenceNode>, options, print);
case SyntaxKind.ValueOfExpression:
return printValueOfExpression(path as AstPath<ValueOfExpressionNode>, options, print);
case SyntaxKind.TemplateParameterDeclaration:
return printTemplateParameterDeclaration(
path as AstPath<TemplateParameterDeclarationNode>,
@ -1230,6 +1233,15 @@ export function printTypeReference(
return [type, template];
}
export function printValueOfExpression(
path: prettier.AstPath<ValueOfExpressionNode>,
options: TypeSpecPrettierOptions,
print: PrettierChildPrint
): prettier.doc.builders.Doc {
const type = path.call(print, "target");
return ["valueof ", type];
}
function printTemplateParameterDeclaration(
path: AstPath<TemplateParameterDeclarationNode>,
options: TypeSpecPrettierOptions,

Просмотреть файл

@ -6,6 +6,7 @@ import {
} from "../core/decorator-utils.js";
import {
StdTypeName,
StringLiteral,
getDiscriminatedUnion,
getTypeName,
ignoreDiagnostics,
@ -744,12 +745,12 @@ export function $withUpdateableProperties(context: DecoratorContext, target: Typ
export function $withoutOmittedProperties(
context: DecoratorContext,
target: Model,
omitProperties: string | Union
omitProperties: StringLiteral | Union
) {
// Get the property or properties to omit
const omitNames = new Set<string>();
if (typeof omitProperties === "string") {
omitNames.add(omitProperties);
if (omitProperties.kind === "String") {
omitNames.add(omitProperties.value);
} else {
for (const variant of omitProperties.variants.values()) {
if (variant.type.kind === "String") {
@ -990,7 +991,9 @@ export function $withDefaultKeyVisibility(
...keyProp.decorators,
{
decorator: $visibility,
args: [{ value: context.program.checker.createLiteralType(visibility) }],
args: [
{ value: context.program.checker.createLiteralType(visibility), jsValue: visibility },
],
},
],
})

Просмотреть файл

@ -14,7 +14,7 @@ namespace TypeSpec;
* model Pet {}
* ```
*/
extern dec summary(target: unknown, summary: string);
extern dec summary(target: unknown, summary: valueof string);
/**
* Attach a documentation string.
@ -27,7 +27,7 @@ extern dec summary(target: unknown, summary: string);
* model Pet {}
* ```
*/
extern dec doc(target: unknown, doc: string, formatArgs?: {});
extern dec doc(target: unknown, doc: valueof string, formatArgs?: {});
/**
* Mark this type as deprecated
@ -40,7 +40,7 @@ extern dec doc(target: unknown, doc: string, formatArgs?: {});
* op Action<T>(): T;
* ```
*/
extern dec deprecated(target: unknown, message: string);
extern dec deprecated(target: unknown, message: valueof string);
/**
* Service options.
@ -107,7 +107,7 @@ extern dec error(target: Model);
* scalar uuid extends string;
* ```
*/
extern dec format(target: string | bytes | ModelProperty, format: string);
extern dec format(target: string | bytes | ModelProperty, format: valueof string);
/**
* Specify the the pattern this string should respect using simple regular expression syntax.
@ -122,7 +122,7 @@ extern dec format(target: string | bytes | ModelProperty, format: string);
* scalar LowerAlpha extends string;
* ```
*/
extern dec pattern(target: string | bytes | ModelProperty, pattern: string);
extern dec pattern(target: string | bytes | ModelProperty, pattern: valueof string);
/**
* Specify the minimum length this string type should be.
@ -134,7 +134,7 @@ extern dec pattern(target: string | bytes | ModelProperty, pattern: string);
* scalar Username extends string;
* ```
*/
extern dec minLength(target: string | ModelProperty, value: integer);
extern dec minLength(target: string | ModelProperty, value: valueof integer);
/**
* Specify the maximum length this string type should be.
@ -146,7 +146,7 @@ extern dec minLength(target: string | ModelProperty, value: integer);
* scalar Username extends string;
* ```
*/
extern dec maxLength(target: string | ModelProperty, value: integer);
extern dec maxLength(target: string | ModelProperty, value: valueof integer);
/**
* Specify the minimum number of items this array should have.
@ -158,7 +158,7 @@ extern dec maxLength(target: string | ModelProperty, value: integer);
* model Endpoints is string[];
* ```
*/
extern dec minItems(target: unknown[] | ModelProperty, value: integer);
extern dec minItems(target: unknown[] | ModelProperty, value: valueof integer);
/**
* Specify the maximum number of items this array should have.
@ -170,7 +170,7 @@ extern dec minItems(target: unknown[] | ModelProperty, value: integer);
* model Endpoints is string[];
* ```
*/
extern dec maxItems(target: unknown[] | ModelProperty, value: integer);
extern dec maxItems(target: unknown[] | ModelProperty, value: valueof integer);
/**
* Specify the minimum value this numeric type should be.
@ -182,7 +182,7 @@ extern dec maxItems(target: unknown[] | ModelProperty, value: integer);
* scalar Age is int32;
* ```
*/
extern dec minValue(target: numeric | ModelProperty, value: numeric);
extern dec minValue(target: numeric | ModelProperty, value: valueof numeric);
/**
* Specify the maximum value this numeric type should be.
@ -194,7 +194,7 @@ extern dec minValue(target: numeric | ModelProperty, value: numeric);
* scalar Age is int32;
* ```
*/
extern dec maxValue(target: numeric | ModelProperty, value: numeric);
extern dec maxValue(target: numeric | ModelProperty, value: valueof numeric);
/**
* Specify the minimum value this numeric type should be, exclusive of the given
@ -207,7 +207,7 @@ extern dec maxValue(target: numeric | ModelProperty, value: numeric);
* scalar distance is float64;
* ```
*/
extern dec minValueExclusive(target: numeric | ModelProperty, value: numeric);
extern dec minValueExclusive(target: numeric | ModelProperty, value: valueof numeric);
/**
* Specify the maximum value this numeric type should be, exclusive of the given
@ -220,7 +220,7 @@ extern dec minValueExclusive(target: numeric | ModelProperty, value: numeric);
* scalar distance is float64;
* ```
*/
extern dec maxValueExclusive(target: numeric | ModelProperty, value: numeric);
extern dec maxValueExclusive(target: numeric | ModelProperty, value: valueof numeric);
/**
* Mark this string as a secret value that should be treated carefully to avoid exposure
@ -237,7 +237,7 @@ extern dec secret(target: string | ModelProperty);
* 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
*/
extern dec tag(target: Namespace | Interface | Operation, tag: string);
extern dec tag(target: Namespace | Interface | Operation, tag: valueof string);
/**
* Specifies how a templated type should name their instances.
@ -253,7 +253,7 @@ extern dec tag(target: Namespace | Interface | Operation, tag: string);
* }
* ```
*/
extern dec friendlyName(target: unknown, name: string, formatArgs?: unknown);
extern dec friendlyName(target: unknown, name: valueof string, formatArgs?: unknown);
/**
* Provide a set of known values to a string type.
@ -283,7 +283,7 @@ extern dec knownValues(target: string | numeric | ModelProperty, values: Enum);
* }
* ```
*/
extern dec key(target: ModelProperty, altName?: string);
extern dec key(target: ModelProperty, altName?: valueof string);
/**
* Specify this operation is an overload of the given operation.
@ -313,7 +313,11 @@ extern dec overload(target: Operation, overloadbase: Operation);
* }
* ```
*/
extern dec projectedName(target: unknown, targetName: string, projectedName: string);
extern dec projectedName(
target: unknown,
targetName: valueof string,
projectedName: valueof string
);
/**
* Specify the property to be used to discriminate this type.
@ -337,7 +341,7 @@ extern dec projectedName(target: unknown, targetName: string, projectedName: str
* model Dog extends Pet {kind: "dog", bark: boolean}
* ```
*/
extern dec discriminator(target: Model | Union, propertyName: string);
extern dec discriminator(target: Model | Union, propertyName: valueof string);
/**
* Known encoding to use on utcDateTime or offsetDateTime
@ -444,7 +448,7 @@ extern dec encode(
* }
* ```
*/
extern dec visibility(target: ModelProperty, ...visibilities: string[]);
extern dec visibility(target: ModelProperty, ...visibilities: valueof string[]);
/**
* Removes properties that are not considered to be present or applicable
@ -486,14 +490,14 @@ extern dec visibility(target: ModelProperty, ...visibilities: string[]);
* }
* ```
*/
extern dec withVisibility(target: Model, ...visibilities: string[]);
extern dec withVisibility(target: Model, ...visibilities: valueof string[]);
/**
* Set the visibility of key properties in a model if not already set.
*
* @param visibility The desired default visibility value. If a key property already has a `visibility` decorator then the default visibility is not applied.
*/
extern dec withDefaultKeyVisibility(target: Model, visibility: unknown);
extern dec withDefaultKeyVisibility(target: Model, visibility: valueof string);
/**
* Returns the model with non-updateable properties removed.
@ -524,10 +528,10 @@ extern dec withoutOmittedProperties(target: Model, omit: string | Union);
* A debugging decorator used to inspect a type.
* @param text Custom text to log
*/
extern dec inspectType(target: unknown, text: string);
extern dec inspectType(target: unknown, text: valueof string);
/**
* A debugging decorator used to inspect a type name.
* @param text Custom text to log
*/
extern dec inspectTypeName(target: unknown, text: string);
extern dec inspectTypeName(target: unknown, text: valueof string);

Просмотреть файл

@ -193,6 +193,6 @@ model OmitDefaults<T> {
*/
@doc("The template for setting the default visibility of key properties.")
@withDefaultKeyVisibility(Visibility)
model DefaultKeyVisibility<T, Visibility extends string> {
model DefaultKeyVisibility<T, Visibility extends valueof string> {
...T;
}

Просмотреть файл

@ -218,6 +218,17 @@ const identifierExpression: MatchRule = {
match: identifier,
};
const valueOfExpression: BeginEndRule = {
key: "valueof",
scope: meta,
begin: `\\b(valueof)`,
beginCaptures: {
"1": { scope: "keyword.other.tsp" },
},
end: `(?=>)|${universalEnd}`,
patterns: [expression],
};
const typeArguments: BeginEndRule = {
key: "type-arguments",
scope: meta,
@ -777,6 +788,7 @@ expression.patterns = [
token,
directive,
parenthesizedExpression,
valueOfExpression,
typeArguments,
tupleExpression,
modelExpression,

Просмотреть файл

@ -14,6 +14,7 @@ import {
SyntaxKind,
Type,
UnionVariant,
ValueType,
} from "../core/types.js";
import { printId } from "../formatter/print/printer.js";
@ -28,7 +29,7 @@ export function getSymbolSignature(program: Program, sym: Sym): string {
return getTypeSignature(type);
}
function getTypeSignature(type: Type): string {
function getTypeSignature(type: Type | ValueType): string {
switch (type.kind) {
case "Scalar":
case "Enum":
@ -39,10 +40,13 @@ function getTypeSignature(type: Type): string {
return fence(`${type.kind.toLowerCase()} ${getPrintableTypeName(type)}`);
case "Decorator":
return fence(getDecoratorSignature(type));
case "Function":
return fence(getFunctionSignature(type));
case "Operation":
return fence(getOperationSignature(type));
case "Value":
return `valueof ${getTypeSignature(type)}`;
case "String":
// BUG: https://github.com/microsoft/typespec/issues/1350 - should escape string literal values
return `(string)\n${fence(`"${type.value}"`)}`;

Просмотреть файл

@ -1,5 +1,5 @@
import { deepEqual, strictEqual } from "assert";
import { Model, Operation, Type } from "../../core/types.js";
import { Model, Operation, StringLiteral, Type } from "../../core/types.js";
import { TestHost, createTestHost, expectDiagnosticEmpty } from "../../testing/index.js";
describe("compiler: checker: augment decorators", () => {
@ -37,8 +37,8 @@ describe("compiler: checker: augment decorators", () => {
let customName: string | undefined;
testHost.addJsFile("test.js", {
$customName(_: any, t: Type, n: string) {
customName = n;
$customName(_: any, t: Type, n: StringLiteral) {
customName = n.value;
},
});
@ -127,9 +127,9 @@ describe("compiler: checker: augment decorators", () => {
let runOnTarget: Type | undefined;
testHost.addJsFile("test.js", {
$customName(_: any, t: Type, n: string) {
$customName(_: any, t: Type, n: StringLiteral) {
runOnTarget = t;
customName = n;
customName = n.value;
},
});
@ -260,18 +260,18 @@ describe("compiler: checker: augment decorators", () => {
let runOnTarget: Type | undefined;
testHost.addJsFile("test.js", {
$customName(_: any, t: Type, n: string) {
$customName(_: any, t: Type, n: StringLiteral) {
runOnTarget = t;
customName = n;
customName = n.value;
},
});
testHost.addTypeSpecFile(
"test.tsp",
`
import "./test.js";
import "./test.js";
${code}
${code}
`
);
@ -319,9 +319,9 @@ describe("compiler: checker: augment decorators", () => {
let runOnTarget: Type | undefined;
testHost.addJsFile("test.js", {
$customName(_: any, t: Type, n: string) {
$customName(_: any, t: Type, n: StringLiteral) {
runOnTarget = t;
customName = n;
customName = n.value;
},
});

Просмотреть файл

@ -136,7 +136,7 @@ describe("compiler: checker: decorators", () => {
it("calls a decorator with arguments", async () => {
const { Foo } = await runner.compile(`
extern dec testDec(target: unknown, arg1: string, arg2: string);
extern dec testDec(target: unknown, arg1: valueof string, arg2: valueof string);
@testDec("one", "two")
@test
@ -148,7 +148,7 @@ describe("compiler: checker: decorators", () => {
it("calls a decorator with optional arguments", async () => {
const { Foo } = await runner.compile(`
extern dec testDec(target: unknown, arg1: string, arg2?: string);
extern dec testDec(target: unknown, arg1: valueof string, arg2?: valueof string);
@testDec("one")
@test
@ -160,7 +160,7 @@ describe("compiler: checker: decorators", () => {
it("calls a decorator with rest arguments", async () => {
const { Foo } = await runner.compile(`
extern dec testDec(target: unknown, arg1: string, ...args: string[]);
extern dec testDec(target: unknown, arg1: valueof string, ...args: valueof string[]);
@testDec("one", "two", "three", "four")
@test

Просмотреть файл

@ -1,5 +1,5 @@
import { deepStrictEqual, ok, strictEqual } from "assert";
import { Diagnostic, Model, ModelPropertyNode, Type } from "../../core/index.js";
import { Diagnostic, FunctionParameterNode, Model, Type } from "../../core/index.js";
import {
BasicTestRunner,
DiagnosticMatch,
@ -19,7 +19,9 @@ interface RelatedTypeOptions {
describe("compiler: checker: type relations", () => {
let runner: BasicTestRunner;
beforeEach(async () => {
runner = createTestWrapper(await createTestHost());
const host = await createTestHost();
host.addJsFile("mock.js", { $mock: () => null });
runner = createTestWrapper(host);
});
async function checkTypeAssignable({ source, target, commonCode }: RelatedTypeOptions): Promise<{
@ -28,20 +30,21 @@ describe("compiler: checker: type relations", () => {
expectedDiagnosticPos: number;
}> {
const { source: code, pos } = extractCursor(`
import "./mock.js";
${commonCode ?? ""}
@test model Test {
source: ${source};
target: ${target};
}`);
const { Test } = (await runner.compile(code)) as { Test: Model };
const sourceProp = Test.properties.get("source")!.type;
const targetProp = Test.properties.get("target")!.type;
extern dec mock(target: unknown, source: ${source}, value: ${target});
`);
await runner.compile(code);
const decDeclaration = runner.program
.getGlobalNamespaceType()
.decoratorDeclarations.get("mock");
const sourceProp = decDeclaration?.parameters[0].type!;
const targetProp = decDeclaration?.parameters[1].type!;
const [related, diagnostics] = runner.program.checker.isTypeAssignableTo(
sourceProp,
targetProp,
(Test.properties.get("source")!.node! as ModelPropertyNode).value
(decDeclaration?.parameters[0].node! as FunctionParameterNode).type!
);
return { related, diagnostics, expectedDiagnosticPos: pos };
}
@ -828,4 +831,157 @@ describe("compiler: checker: type relations", () => {
});
testReflectionType("UnionVariant", "Foo.a", `union Foo {a: string, b: int32};`);
});
describe("Value target", () => {
describe("valueof string", () => {
it("can assign string literal", async () => {
await expectTypeAssignable({ source: `"foo bar"`, target: "valueof string" });
});
it("cannot assign numeric literal", async () => {
await expectTypeNotAssignable(
{ source: `123`, target: "valueof string" },
{
code: "unassignable",
message: "Type '123' is not assignable to type 'string'",
}
);
});
it("cannot assign string scalar", async () => {
await expectTypeNotAssignable(
{ source: `string`, target: "valueof string" },
{
code: "unassignable",
message: "Type 'string' is not assignable to type 'valueof string'",
}
);
});
});
describe("valueof boolean", () => {
it("can assign boolean literal", async () => {
await expectTypeAssignable({ source: `true`, target: "valueof boolean" });
});
it("cannot assign numeric literal", async () => {
await expectTypeNotAssignable(
{ source: `123`, target: "valueof boolean" },
{
code: "unassignable",
message: "Type '123' is not assignable to type 'boolean'",
}
);
});
it("cannot assign boolean scalar", async () => {
await expectTypeNotAssignable(
{ source: `boolean`, target: "valueof boolean" },
{
code: "unassignable",
message: "Type 'boolean' is not assignable to type 'valueof boolean'",
}
);
});
});
describe("valueof int16", () => {
it("can assign int16 literal", async () => {
await expectTypeAssignable({ source: `12`, target: "valueof int16" });
});
it("can assign valueof int8", async () => {
await expectTypeAssignable({ source: `valueof int8`, target: "valueof int16" });
});
it("cannot assign int too large", async () => {
await expectTypeNotAssignable(
{ source: `123456`, target: "valueof int16" },
{
code: "unassignable",
message: "Type '123456' is not assignable to type 'int16'",
}
);
});
it("cannot assign float", async () => {
await expectTypeNotAssignable(
{ source: `12.6`, target: "valueof int16" },
{
code: "unassignable",
message: "Type '12.6' is not assignable to type 'int16'",
}
);
});
it("cannot assign string literal", async () => {
await expectTypeNotAssignable(
{ source: `"foo bar"`, target: "valueof int16" },
{
code: "unassignable",
message: "Type 'foo bar' is not assignable to type 'int16'",
}
);
});
it("cannot assign int16 scalar", async () => {
await expectTypeNotAssignable(
{ source: `int16`, target: "valueof int16" },
{
code: "unassignable",
message: "Type 'int16' is not assignable to type 'valueof int16'",
}
);
});
});
describe("valueof float32", () => {
it("can assign float32 literal", async () => {
await expectTypeAssignable({ source: `12.6`, target: "valueof float32" });
});
it("cannot assign string literal", async () => {
await expectTypeNotAssignable(
{ source: `"foo bar"`, target: "valueof float32" },
{
code: "unassignable",
message: "Type 'foo bar' is not assignable to type 'float32'",
}
);
});
it("cannot assign float32 scalar", async () => {
await expectTypeNotAssignable(
{ source: `float32`, target: "valueof float32" },
{
code: "unassignable",
message: "Type 'float32' is not assignable to type 'valueof float32'",
}
);
});
});
it("can use valueof in template parameter constraints", async () => {
const diagnostics = await runner.diagnose(`
model Foo<T extends valueof string> {
@doc(T)
prop1: int16;
}`);
expectDiagnosticEmpty(diagnostics);
});
// BackCompat added May 2023 Sprint: by June 2023 sprint. From this PR: https://github.com/microsoft/typespec/pull/1877
it("BACKCOMPAT: can use valueof in template parameter constraints", async () => {
const diagnostics = await runner.diagnose(`
model Foo<T extends string> {
@doc(T)
prop1: int16;
}`);
expectDiagnostics(diagnostics, {
code: "deprecated",
message:
"Deprecated: Template constrainted to 'string' will not be assignable to 'valueof string' in the future. Update the constraint to be 'valueof string'",
});
});
});
});

Просмотреть файл

@ -40,7 +40,7 @@ describe("compiler: scalars", () => {
const { A } = await runner.compile(`
@doc(T)
@test
scalar A<T extends string>;
scalar A<T extends valueof string>;
alias B = A<"123">;
`);
@ -49,11 +49,11 @@ describe("compiler: scalars", () => {
strictEqual(A.name, "A");
});
// https://github.com/microsoft/typespec/issues/1764
// Test for https://github.com/microsoft/typespec/issues/1764
it("template parameter are scoped to the scalar", async () => {
const { A, B } = await runner.compile(`
@test @doc(T) scalar A<T extends string>;
@test @doc(T) scalar B<T extends string>;
@test @doc(T) scalar A<T extends valueof string>;
@test @doc(T) scalar B<T extends valueof string>;
alias AIns = A<"">;
alias BIns = B<"">;

Просмотреть файл

@ -139,7 +139,7 @@ describe("compiler: built-in decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: `Argument '123' is not assignable to parameter of type 'string'`,
message: `Argument '123' is not assignable to parameter of type 'valueof string'`,
});
});
});
@ -296,7 +296,7 @@ describe("compiler: built-in decorators", () => {
expectDiagnostics(diagnostics, [
{
code: "invalid-argument",
message: "Argument '4' is not assignable to parameter of type 'string'",
message: "Argument '4' is not assignable to parameter of type 'valueof string'",
},
]);
});

Просмотреть файл

@ -218,6 +218,15 @@ describe("compiler: parser", () => {
]);
});
describe("valueof expressions", () => {
parseEach([
"alias A = valueof string;",
"alias A = valueof int32;",
"alias A = valueof {a: string, b: int32};",
"alias A = valueof int8[];",
]);
});
describe("template instantiations", () => {
parseEach(["model A { x: Foo<number, string>; }", "model B { x: Foo<number, string>[]; }"]);
});

Просмотреть файл

@ -56,6 +56,7 @@ const Token = {
else: createToken("else", "keyword.other.tsp"),
to: createToken("to", "keyword.other.tsp"),
from: createToken("from", "keyword.other.tsp"),
valueof: createToken("valueof", "keyword.other.tsp"),
other: (text: string) => createToken(text, "keyword.other.tsp"),
},
@ -159,6 +160,24 @@ function testColorization(description: string, tokenize: Tokenize) {
});
});
describe("valueof", () => {
it("simple valueof", async () => {
const tokens = await tokenize("model Foo<T extends valueof string> {}");
deepStrictEqual(tokens, [
Token.keywords.model,
Token.identifiers.type("Foo"),
Token.punctuation.typeParameters.begin,
Token.identifiers.type("T"),
Token.keywords.extends,
Token.keywords.valueof,
Token.identifiers.type("string"),
Token.punctuation.typeParameters.end,
Token.punctuation.openBrace,
Token.punctuation.closeBrace,
]);
});
});
describe("decorators", () => {
it("simple parameterless decorator", async () => {
const tokens = await tokenize("@foo");

Просмотреть файл

@ -235,7 +235,7 @@ describe("compiler: server: completion", () => {
kind: CompletionItemKind.Function,
documentation: {
kind: MarkupKind.Markdown,
value: "```typespec\ndec doc(target: unknown, doc: string, formatArgs?: {})\n```",
value: "```typespec\ndec doc(target: unknown, doc: valueof string, formatArgs?: {})\n```",
},
},
]);
@ -254,7 +254,7 @@ describe("compiler: server: completion", () => {
kind: CompletionItemKind.Function,
documentation: {
kind: MarkupKind.Markdown,
value: "```typespec\ndec doc(target: unknown, doc: string, formatArgs?: {})\n```",
value: "```typespec\ndec doc(target: unknown, doc: valueof string, formatArgs?: {})\n```",
},
},
]);
@ -292,7 +292,7 @@ describe("compiler: server: completion", () => {
kind: CompletionItemKind.Function,
documentation: {
kind: MarkupKind.Markdown,
value: "```typespec\ndec doc(target: unknown, doc: string, formatArgs?: {})\n```",
value: "```typespec\ndec doc(target: unknown, doc: valueof string, formatArgs?: {})\n```",
},
},
]);

Просмотреть файл

@ -9,7 +9,7 @@ import { NodeHost } from "../core/node-host.js";
import { CompilerOptions } from "../core/options.js";
import { getAnyExtensionFromPath, resolvePath } from "../core/path-utils.js";
import { Program, compile as compileProgram } from "../core/program.js";
import { CompilerHost, Diagnostic, Type } from "../core/types.js";
import { CompilerHost, Diagnostic, StringLiteral, Type } from "../core/types.js";
import { createStringMap, getSourceFileKindFromExt } from "../core/util.js";
import { expectDiagnosticEmpty } from "./expect.js";
import { createTestWrapper } from "./test-utils.js";
@ -253,7 +253,8 @@ async function createTestHostInternal(): Promise<TestHost> {
fileSystem.addTypeSpecFile(".tsp/test-lib/main.tsp", 'import "./test.js";');
fileSystem.addJsFile(".tsp/test-lib/test.js", {
namespace: "TypeSpec",
$test(_: any, target: Type, name?: string) {
$test(_: any, target: Type, nameLiteral?: StringLiteral) {
let name = nameLiteral?.value;
if (!name) {
if (
target.kind === "Model" ||

Просмотреть файл

@ -70,7 +70,7 @@ extern dec query(target: ModelProperty, queryNameOrOptions?: string | QueryOptio
* op read(@path explicit: string, implicit: string): void;
* ```
*/
extern dec path(target: ModelProperty, paramName?: string);
extern dec path(target: ModelProperty, paramName?: valueof string);
/**
* Explicitly specify that this property is to be set as the body
@ -186,8 +186,8 @@ extern dec head(target: Operation);
*/
extern dec server(
target: Namespace,
url: string,
description: string,
url: valueof string,
description: valueof string,
parameters?: Record<unknown>
);
@ -209,7 +209,7 @@ extern dec useAuth(target: Namespace, auth: {} | Union | {}[]);
* Specify if inapplicable metadata should be included in the payload for the given entity.
* @param value If true, inapplicable metadata will be included in the payload.
*/
extern dec includeInapplicableMetadataInPayload(target: unknown, value: boolean);
extern dec includeInapplicableMetadataInPayload(target: unknown, value: valueof boolean);
/**
* Defines the relative route URI for the target operation
@ -232,7 +232,7 @@ extern dec includeInapplicableMetadataInPayload(target: unknown, value: boolean)
*/
extern dec route(
target: Namespace | Interface | Operation,
path: string,
path: valueof string,
options?: {
shared?: boolean,
}

Просмотреть файл

@ -7,6 +7,7 @@ import {
Namespace,
Operation,
Program,
StringLiteral,
Tuple,
Type,
Union,
@ -38,15 +39,15 @@ const headerFieldsKey = createStateSymbol("header");
export function $header(
context: DecoratorContext,
entity: ModelProperty,
headerNameOrOptions?: string | Model
headerNameOrOptions?: StringLiteral | Model
) {
const options: HeaderFieldOptions = {
type: "header",
name: entity.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
};
if (headerNameOrOptions) {
if (typeof headerNameOrOptions === "string") {
options.name = headerNameOrOptions;
if (headerNameOrOptions.kind === "String") {
options.name = headerNameOrOptions.value;
} else {
const name = headerNameOrOptions.properties.get("name")?.type;
if (name?.kind === "String") {
@ -89,15 +90,15 @@ const queryFieldsKey = createStateSymbol("query");
export function $query(
context: DecoratorContext,
entity: ModelProperty,
queryNameOrOptions?: string | Model
queryNameOrOptions?: StringLiteral | Model
) {
const options: QueryParameterOptions = {
type: "query",
name: entity.name,
};
if (queryNameOrOptions) {
if (typeof queryNameOrOptions === "string") {
options.name = queryNameOrOptions;
if (queryNameOrOptions.kind === "String") {
options.name = queryNameOrOptions.value;
} else {
const name = queryNameOrOptions.properties.get("name")?.type;
if (name?.kind === "String") {

Просмотреть файл

@ -343,7 +343,7 @@ describe("http: decorators", () => {
expectDiagnostics(diagnostics, [
{
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
},
]);
});
@ -462,7 +462,7 @@ describe("http: decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
});
});
@ -474,7 +474,7 @@ describe("http: decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
});
});

Просмотреть файл

@ -14,7 +14,7 @@ namespace OpenAPI;
* op read(): string;
* ```
*/
extern dec operationId(target: Operation, operationId: string);
extern dec operationId(target: Operation, operationId: valueof string);
/**
* Attach some custom data to the OpenAPI element generated from this type.
@ -30,7 +30,7 @@ extern dec operationId(target: Operation, operationId: string);
* op read(): string;
* ```
*/
extern dec extension(target: unknown, key: string, value: unknown);
extern dec extension(target: unknown, key: valueof string, value: unknown);
/**
* Specify that this model is to be treated as the OpenAPI `default` response.
@ -59,4 +59,4 @@ extern dec defaultResponse(target: Model);
* op listPets(): Pet[];
* ```
*/
extern dec externalDocs(target: unknown, url: string, description?: string);
extern dec externalDocs(target: unknown, url: valueof string, description?: valueof string);

Просмотреть файл

@ -32,7 +32,7 @@ describe("openapi: decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
});
});
});
@ -77,7 +77,7 @@ describe("openapi: decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
});
});
@ -106,7 +106,7 @@ describe("openapi: decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
});
});
@ -119,7 +119,7 @@ describe("openapi: decorators", () => {
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' is not assignable to parameter of type 'string'",
message: "Argument '123' is not assignable to parameter of type 'valueof string'",
});
});

Просмотреть файл

@ -175,7 +175,7 @@ extern dec message(target: {});
* }
* ```
*/
extern dec field(target: TypeSpec.Reflection.ModelProperty, index: uint32);
extern dec field(target: TypeSpec.Reflection.ModelProperty, index: valueof uint32);
/**
* Reserve a field index, range, or name. If a field definition collides with a reservation, the emitter will produce
@ -211,7 +211,7 @@ extern dec field(target: TypeSpec.Reflection.ModelProperty, index: uint32);
* }
* ```
*/
extern dec reserve(target: {}, ...reservations: (string | [uint32, uint32] | uint32)[]);
extern dec reserve(target: {}, ...reservations: valueof (string | [uint32, uint32] | uint32)[]);
/**
* Declares that a TypeSpec interface constitutes a Protobuf service. The contents of the interface will be converted to

Просмотреть файл

@ -14,6 +14,7 @@ import {
Operation,
Program,
resolvePath,
StringLiteral,
Tuple,
Type,
} from "@typespec/compiler";
@ -91,8 +92,13 @@ export function $_map(ctx: DecoratorContext, target: Model) {
ctx.program.stateSet(state._map).add(target);
}
export function $externRef(ctx: DecoratorContext, target: Model, path: string, name: string) {
ctx.program.stateMap(state.externRef).set(target, [path, name]);
export function $externRef(
ctx: DecoratorContext,
target: Model,
path: StringLiteral,
name: StringLiteral
) {
ctx.program.stateMap(state.externRef).set(target, [path.value, name.value]);
}
export function $stream(ctx: DecoratorContext, target: Operation, mode: EnumMember) {

Просмотреть файл

@ -12,10 +12,14 @@ import {
TemplateParameterDeclarationNode,
Type,
UnionVariant,
ValueType,
} from "@typespec/compiler";
/** @internal */
export function getTypeSignature(type: Type): string {
export function getTypeSignature(type: Type | ValueType): string {
if (type.kind === "Value") {
return `valueof ${getTypeSignature(type.target)}`;
}
if (isReflectionType(type)) {
return type.name;
}
@ -42,6 +46,7 @@ export function getTypeSignature(type: Type): string {
return `(number) ${type.value.toString()}`;
case "Intrinsic":
return `(intrinsic) ${type.name}`;
case "FunctionParameter":
return getFunctionParameterSignature(type);
case "ModelProperty":

Просмотреть файл

@ -27,7 +27,7 @@ extern dec autoRoute(target: Interface | Operation);
* get(@segment("pets") @path id: string): void; //-> route: /pets/{id}
* }
*/
extern dec segment(target: Model | ModelProperty | Operation, name: string);
extern dec segment(target: Model | ModelProperty | Operation, name: valueof string);
/**
* Returns the URL segment of a given model if it has `@segment` and `@key` decorator.
@ -40,14 +40,17 @@ extern dec segmentOf(target: Operation, type: Model);
*
* @param seperator Seperator seperating the action segment from the rest of the url
*/
extern dec actionSeparator(target: Model | ModelProperty | Operation, seperator: "/" | ":" | "/:");
extern dec actionSeparator(
target: Model | ModelProperty | Operation,
seperator: valueof "/" | ":" | "/:"
);
/**
* Mark this model as a resource type with a name.
*
* @param collectionName type's collection name
*/
extern dec resource(target: Model, collectionName: string);
extern dec resource(target: Model, collectionName: valueof string);
/**
* Mark model as a child of the given parent resource.
@ -108,20 +111,20 @@ extern dec listsResource(target: Operation, resourceType: Model);
* Specify this operation is an action. (Scoped to a resource item /pets/{petId}/my-action)
* @param name Name of the action. If not specified, the name of the operation will be used.
*/
extern dec action(target: Operation, name?: string);
extern dec action(target: Operation, name?: valueof string);
/**
* Specify this operation is a collection action. (Scopped to a resource, /pets/my-action)
* @param resourceType Resource marked with @resource
* @param name Name of the action. If not specified, the name of the operation will be used.
*/
extern dec collectionAction(target: Operation, resourceType: Model, name?: string);
extern dec collectionAction(target: Operation, resourceType: Model, name?: valueof string);
/**
* Copy the resource key parameters on the model
* @param filter Filter to exclude certain properties.
*/
extern dec copyResourceKeyParameters(target: Model, filter?: string);
extern dec copyResourceKeyParameters(target: Model, filter?: valueof string);
namespace Private {
extern dec resourceLocation(target: string, resourceType: Model);

Просмотреть файл

@ -108,7 +108,7 @@ function cloneKeyProperties(context: DecoratorContext, target: Model, resourceTy
},
{
decorator: $resourceTypeForKeyParam,
args: [{ node: target.node, value: resourceType }],
args: [{ node: target.node, value: resourceType, jsValue: resourceType }],
},
];

Просмотреть файл

@ -286,7 +286,9 @@ export function $resource(context: DecoratorContext, entity: Model, collectionNa
// Manually push the decorator onto the property so that it's copyable in KeysOf<T>
key.keyProperty.decorators.push({
decorator: $segment,
args: [{ value: context.program.checker.createLiteralType(collectionName) }],
args: [
{ value: context.program.checker.createLiteralType(collectionName), jsValue: collectionName },
],
});
}

Просмотреть файл

@ -424,7 +424,7 @@ describe("rest: routes", () => {
strictEqual(diagnostics[0].code, "invalid-argument");
strictEqual(
diagnostics[0].message,
`Argument 'x' is not assignable to parameter of type '/ | : | /:'`
`Argument 'x' is not assignable to parameter of type 'valueof / | : | /:'`
);
});

Просмотреть файл

@ -2,11 +2,11 @@
import { $doc } from "@typespec/compiler";
export function $fancyDoc(program, target, text) {
text = `<blink>${text}</blink>`;
$doc(program, target, text);
const str = `<blink>${text.value}</blink>`;
$doc(program, target, str);
}
export function $evenFancierDoc(program, target, ...args) {
args[0] = `<marquee><blink>${args[0]}</blink></marquee>`;
args[0] = `<marquee><blink>${args[0].value}</blink></marquee>`;
$doc(program, target, ...args);
}

Просмотреть файл

@ -437,8 +437,12 @@ UnionExpressionOrHigher :
`|`? UnionExpressionOrHigher `|` IntersectionExpressionOrHigher
IntersectionExpressionOrHigher :
ValueOfExpressionOrHigher
`&`? IntersectionExpressionOrHigher `&` ValueOfExpressionOrHigher
ValueOfExpressionOrHigher :
ArrayExpressionOrHigher
`&`? IntersectionExpressionOrHigher `&` ArrayExpressionOrHigher
`valueof` ArrayExpressionOrHigher
ArrayExpressionOrHigher :
PrimaryExpression

Просмотреть файл

@ -33,7 +33,7 @@ namespace TypeSpec {
* @param version The version that the target was renamed in.
* @param oldName The previous name of the target.
*/
extern dec renamedFrom(target: unknown, version: EnumMember, oldName: string);
extern dec renamedFrom(target: unknown, version: EnumMember, oldName: valueof string);
/**
* Identifies when a target was made optional.

Просмотреть файл

@ -14,6 +14,7 @@
{ "path": "packages/internal-build-utils/tsconfig.json" },
{ "path": "packages/tmlanguage-generator/tsconfig.json" },
{ "path": "packages/html-program-viewer/tsconfig.json" },
{ "path": "packages/protobuf/tsconfig.json" },
{ "path": "packages/openapi3/tsconfig.json" },
{ "path": "packages/bundler/tsconfig.json" },
{ "path": "packages/playground/tsconfig.json" },