Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
442f8dc
chore: scaffold @basketry/typespec package
kyleamazza Mar 13, 2026
a4afb97
feat: add utility modules (location, violations, type-mapping)
kyleamazza Mar 13, 2026
491c672
feat: add petstore TypeSpec fixture
kyleamazza Mar 13, 2026
a7caf70
feat: implement core TypeSpec parser with HTTP protocol support
kyleamazza Mar 13, 2026
48d69ae
feat: add exhaustive multi-file TypeSpec fixture
kyleamazza Mar 13, 2026
1c08d0a
fix: emit violation for anonymous unions, early return on compiler er…
kyleamazza Mar 13, 2026
74f221e
feat: add scalar validation rules tests and ScalarShowcase fixture
kyleamazza Mar 13, 2026
b016fc0
feat: add snapshot generation and snapshot tests
kyleamazza Mar 13, 2026
9aeb6f5
feat: add diagnostic and error handling tests
kyleamazza Mar 13, 2026
d127899
chore: fix lint errors and formatting
kyleamazza Mar 13, 2026
d095a81
chore: convert to ESM output for @typespec/compiler compatibility
kyleamazza Mar 13, 2026
ac27396
docs: add README with installation, usage, and feature documentation
kyleamazza Mar 13, 2026
75297bf
fix: return absolute paths in sourcePaths for Basketry resolution
kyleamazza Mar 13, 2026
a949023
feat: map TypeSpec doc comments to IR description fields
kyleamazza Mar 13, 2026
ae6fc9c
chore: migrate Jest to Vitest and add CJS compatibility wrapper
kyleamazza Mar 13, 2026
1408467
feat: narrow name loc encoding to identifier spans
kyleamazza Mar 13, 2026
b319f9c
fix: add loc to ReturnValue for response-envelope rule
kyleamazza Mar 13, 2026
bff7731
feat: extract @extension decorators as method/type meta
kyleamazza Mar 13, 2026
a8901ab
fix: narrow node loc to name identifier for methods, types, enums, un…
kyleamazza Mar 13, 2026
afe35de
Merge new typespec implementation
kyleamazza Mar 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,3 @@ dist
.pnp.*

lib/
tmp/
151 changes: 143 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,150 @@
[![main](https://github.com/basketry/typespec/workflows/build/badge.svg?branch=main&event=push)](https://github.com/basketry/typespec/actions?query=workflow%3Abuild+branch%3Amain+event%3Apush)
[![main](https://img.shields.io/npm/v/@basketry/typespec)](https://www.npmjs.com/package/@basketry/typespec)
[![master](https://img.shields.io/npm/v/@basketry/typespec)](https://www.npmjs.com/package/@basketry/typespec)

# TypeSpec Parser
# TypeSpec

[Basketry parser](https://github.com/basketry/basketry) for [TypeSpec](https://typespec.io/) service definitions. This parser can be coupled with any Basketry generator to translate a [TypeSpec](https://typespec.io/) document into other artifacts including servers, clients, and human-readable documentation.
[Basketry parser](https://github.com/basketry/basketry) for [TypeSpec](https://typespec.io/) service definitions. This parser can be coupled with any Basketry generator to translate a TypeSpec project into other artifacts including servers, clients, and human-readable documentation.

## Quick Start

TODO
The following example converts a TypeSpec service definition into TypeScript types:

1. Create a `main.tsp` file in the root of your project (see [example](#example-typespec-file) below).
1. Install packages: `npm install basketry @basketry/typespec @basketry/typescript`
1. Generate code: `npx basketry --source main.tsp --parser @basketry/typespec --generators @basketry/typescript --output src`

When the last step is run, Basketry will parse the source file (`main.tsp`) using the specified parser (`@basketry/typespec`) and then run each specified generator (in this case only `@basketry/typescript`) writing to the output folder (`src`).

## Config File

You can use a config file instead of passing command line arguments. Create a `basketry.config.json` file in the root of your project:

```json
{
"source": "main.tsp",
"parser": "@basketry/typespec",
"generators": ["@basketry/typescript"],
"output": "src"
}
```

Then run: `npx basketry`

## Multi-File TypeSpec Projects

The parser fully supports multi-file TypeSpec projects. Point the `source` to your entry file (typically `main.tsp`) and the TypeSpec compiler will resolve all imports automatically:

```
my-api/
main.tsp <-- source entry point
models.tsp
operations.tsp
```

```json
{
"source": "my-api/main.tsp",
"parser": "@basketry/typespec",
"generators": ["@basketry/typescript"],
"output": "src"
}
```

Source locations in the generated IR will correctly reference the originating `.tsp` file for each type, operation, and parameter.

## TypeSpec Features Supported

| Feature | Support |
| --------------------------------------- | -------------------------------- |
| Models and properties | Full |
| Model inheritance (`extends`) | Full (flattened) |
| Enums (string and numeric) | Full (numeric coerced to string) |
| Named unions | Full |
| Nullable types (`T \| null`) | Full |
| Arrays (`T[]`) | Full |
| Scalars (all built-in types) | Full (with validation rules) |
| HTTP operations (`@get`, `@post`, etc.) | Full |
| Path, query, header parameters | Full |
| Request/response bodies | Full |
| Authentication (`@useAuth`) | Bearer, Basic, API Key, OAuth2 |
| Multi-file imports | Full |
| `@service` decorator | Title extraction |
| Compiler diagnostics | Forwarded as violations |

## Type Mapping

TypeSpec scalars are mapped to Basketry IR primitives. When a TypeSpec scalar is richer than the IR primitive (e.g., `int8` mapped to `integer`), the parser attaches validation rules (like `NumberGTE`/`NumberLTE`) to preserve the constraints, and emits an informational `typespec/type-coercion` violation.

| TypeSpec Scalar | IR Primitive | Validation Rules |
| --------------- | ------------ | ----------------------------------- |
| `string` | `string` | |
| `boolean` | `boolean` | |
| `int32` | `integer` | |
| `int64` | `long` | |
| `float32` | `float` | |
| `float64` | `double` | |
| `int8` | `integer` | NumberGTE(-128), NumberLTE(127) |
| `int16` | `integer` | NumberGTE(-32768), NumberLTE(32767) |
| `uint8` | `integer` | NumberGTE(0), NumberLTE(255) |
| `uint16` | `integer` | NumberGTE(0), NumberLTE(65535) |
| `uint32` | `long` | NumberGTE(0), NumberLTE(4294967295) |
| `uint64` | `long` | NumberGTE(0) |
| `plainDate` | `date` | |
| `utcDateTime` | `date-time` | |
| `plainTime` | `string` | StringFormat(time) |
| `duration` | `string` | StringFormat(duration) |
| `url` | `string` | StringFormat(uri) |
| `bytes` | `binary` | |
| `decimal` | `number` | StringFormat(decimal) |

## Violations

The parser emits the following violation codes:

| Code | Severity | Description |
| ------------------------------ | -------- | --------------------------------------------------------------------- |
| `typespec/type-coercion` | info | A TypeSpec scalar was mapped to a coarser IR primitive |
| `typespec/unsupported-feature` | warning | A TypeSpec feature is not supported by the parser |
| `typespec/missing-version` | warning | No version information found in the source |
| `typespec/source-not-found` | error | The source file could not be found |
| `typespec/numeric-enum` | info | Enum has numeric members that were coerced to strings |
| `typespec/*` | varies | TypeSpec compiler diagnostics are forwarded with a `typespec/` prefix |

## Example TypeSpec File

```typespec
import "@typespec/http";

using TypeSpec.Http;

@service(#{
title: "Pet Store Service",
})
@useAuth(BearerAuth)
namespace PetStore;

enum PetStatus {
available: "available",
pending: "pending",
sold: "sold",
}

model Pet {
id: int64;
name: string;
tag?: string;
status: PetStatus;
}

@route("/pets")
interface Pets {
@get list(@query limit?: int32): Pet[];
@post create(@body pet: Pet): Pet;
@get read(@path petId: int64): Pet;
@put update(@path petId: int64, @body pet: Pet): Pet;
@delete delete(@path petId: int64): void;
}
```

---

Expand All @@ -27,6 +164,8 @@ Note that the `lint` script is run prior to `build`. Auto-fixable linting or for
1. Run the tests: `npm t`
1. Test coverage can be viewed at `/coverage/lcov-report/index.html`

Tests use [Vitest](https://vitest.dev/) which handles ESM natively.

### Publish a new package version

1. Create new version
Expand All @@ -36,7 +175,3 @@ Note that the `lint` script is run prior to `build`. Auto-fixable linting or for
1. Publish to NPM
1. Review and merge the PR
1. The [publish workflow](https://github.com/basketry/typespec/actions/workflows/publish.yml) will create a git tag and publish the package on NPM

---

Generated with [generator-ts-console](https://www.npmjs.com/package/generator-ts-console)
7 changes: 1 addition & 6 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ import prettierConfig from 'eslint-config-prettier';
import importPlugin from 'eslint-plugin-import';

export default [
{ ignores: ['lib/**', 'coverage/**', 'src/snapshot/*-snapshot.json'] },
prettierConfig,
{
files: ['**/*.ts', '**/*.js', '**/*.json'],
ignores: [
'lib/**',
'coverage/**',
'src/snapshot/service.json',
'tsp-output/**',
],
languageOptions: {
parser,
},
Expand Down
Loading
Loading