Detach linter from `$lib` and add state declaration to `$lib` (#2773)

fix #2301 

## Deteach the linter from `$lib`

Having the linter rules defined in `$lib` caused this circular reference
where the rules would call to some accessor that needed the $lib
diagnostics.

With this we keep $lib as manifest and helper functions only.

## Add state key declaration
```ts
export const internalLib = createTypeSpecLibrary({
 state: {
    authentication: { description: "State for the @auth decorator" },
    header: { description: "State for the @header decorator" },
    ...
});

// use with StateKeys.authentication
```
This commit is contained in:
Timothee Guerin 2023-12-22 17:27:00 -08:00 коммит произвёл GitHub
Родитель 204083faa0
Коммит da99aa955b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 257 добавлений и 140 удалений

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

@ -0,0 +1,15 @@
{
"changes": [
{
"packageName": "@typespec/compiler",
"comment": "Library declaration: Deprecated linter property on `$lib` in favor of a new `$linter` variable that can be exported. This was done to prevent circular reference caused by referencing linter rules in $lib.",
"type": "none"
},
{
"packageName": "@typespec/compiler",
"comment": "Library declaration: State symbols can now be declared in the library declaration(Prefereably internal declaration added above) to have a central place to define state symbols used in your libraries.",
"type": "none"
}
],
"packageName": "@typespec/compiler"
}

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

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/http",
"comment": "Migrate to new Internal/Public library definition",
"type": "none"
}
],
"packageName": "@typespec/http"
}

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

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/playground",
"comment": "Handle new `$linter` exported variable",
"type": "none"
}
],
"packageName": "@typespec/playground"
}

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

@ -114,7 +114,7 @@ export const $lib = createTypeSpecLibrary({
} as const);
// Optional but convenient, those are meant to be used locally in your library.
export const { reportDiagnostic, createDiagnostic, createStateSymbol } = $lib;
export const { reportDiagnostic, createDiagnostic } = $lib;
```
Diagnostics are used for linters and decorators which are covered in subsequent topics.

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

@ -172,12 +172,12 @@ Decorators can be used to register some metadata. For this you can use the `cont
❌ Do not save the data in a global variable.
```ts
```ts file=decorators.ts
import type { DecoratorContext, Type } from "@typespec/compiler";
import type { createStateSymbol } from "./lib.js";
import type { StateKeys } from "./lib.js";
// Create a unique key
const key = createStateSymbol("customName");
const key = StateKeys.customName;
export function $customName(context: DecoratorContext, target: Type, name: string) {
// Keep a mapping between the target and a value.
context.program.stateMap(key).set(target, name);
@ -187,6 +187,17 @@ export function $customName(context: DecoratorContext, target: Type, name: strin
}
```
```ts file=lib.ts
export const $lib = createTypeSpecLibrary({
// ...
state: {
customName: { description: "State for the @customName decorator" },
},
});
export const StateKeys = $lib.stateKeys;
```
### Reporting diagnostic on decorator or arguments
Decorator context provide the `decoratorTarget` and `getArgumentTarget` helpers

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

@ -23,8 +23,10 @@ TypeSpec compiler report errors and warnings in the spec using the diagnostic AP
### Declare the diagnostics you are reporting
```ts
import { createTypeSpecLibrary } from "@typespec/compiler";
// in lib.js
export const { reportDiagnostic, createDiagnostic, createStateSymbol } = createTypeSpecLibrary({
export const $lib = createTypeSpecLibrary({
name: "@typespec/my-lib",
diagnostics: {
// Basic diagnostic with a fixed message
@ -53,6 +55,9 @@ export const { reportDiagnostic, createDiagnostic, createStateSymbol } = createT
},
},
} as const);
// Rexport the helper functions to be able to just call them directly.
export const { reportDiagnostic, createDiagnostic };
```
This will represent 3 different diagnostics with full name of

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

@ -51,9 +51,26 @@ To pass your emitter custom options, the options must be registered with the com
The following example extends the hello world emitter to be configured with a name:
```typescript
import { JSONSchemaType, createTypeSpecLibrary } from "@typespec/compiler";
import Path from "path";
```ts file=src/internal-lib.ts
export const $lib = createTypeSpecLibrary({
name: "MyEmitter",
diagnostics: {
// Add diagnostics here.
},
state: {
// Add state keys here for decorators.
},
});
```
```ts file=src/lib.ts
import {
JSONSchemaType,
createTypeSpecLibrary,
EmitContext,
resolvePath,
} from "@typespec/compiler";
import { internalLib } from "./lib.js";
export interface EmitterOptions {
"target-name": string;
@ -69,15 +86,14 @@ const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
};
export const $lib = createTypeSpecLibrary({
name: "MyEmitter",
diagnostics: {},
internal: internalLib,
emitter: {
options: EmitterOptionsSchema,
},
});
export async function $onEmit(context: EmitContext<EmitterOptions>) {
const outputDir = Path.join(context.emitterOutputDir, "hello.txt");
const outputDir = resolvePath(context.emitterOutputDir, "hello.txt");
const name = context.options.targetName;
await context.program.host.writeFile(outputDir, `hello ${name}!`);
}
@ -149,15 +165,14 @@ The following example will emit models with the `@emitThis` decorator and also a
[See creating decorator documentation for more details](./create-decorators.md)
```typescript
import { DecoratorContext, Model, createStateSymbol } from "@typespec/compiler";
import { DecoratorContext, Model } from "@typespec/compiler";
import { StateKeys } from "./lib.js";
// Decorator Setup Code
const emitThisKey = createStateSymbol("emitThis");
// @emitThis decorator
export function $emitThis(context: DecoratorContext, target: Model) {
context.program.stateSet(emitThisKey).add(target);
context.program.stateSet(StateKeys.emitThis).add(target);
}
export async function $onEmit(context: EmitContext) {

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

@ -92,32 +92,33 @@ context.reportDiagnostic({
<!-- cspell:disable-next-line -->
When defining your `$lib` with `createTypeSpecLibrary`([See](./basics.md#4-create-libts)) an additional entry for `linter` can be provided
Export a `$linter` variable from your library entrypoint:
```ts
```ts title="index.ts"
export { $linter } from "./linter.js";
```
```ts title="linter.ts"
import { defineLinter } from "@typespec/compiler";
// Import the rule defined previously
import { requiredDocRule } from "./rules/required-doc.rule.js";
export const $lib = createTypeSpecLibrary({
name: "@typespec/my-linter",
diagnostics: {},
linter: {
// Include all the rules your linter is defining here.
rules: [requiredDocRule],
export const $linter = defineLinter({
// Include all the rules your linter is defining here.
rules: [requiredDocRule],
// Optionally a linter can provide a set of rulesets
ruleSets: {
recommended: {
// (optional) A ruleset takes a map of rules to explicitly enable
enable: { [`@typespec/my-linter/${requiredDocRule.name}`]: true },
// Optionally a linter can provide a set of rulesets
ruleSets: {
recommended: {
// (optional) A ruleset takes a map of rules to explicitly enable
enable: { [`@typespec/my-linter/${requiredDocRule.name}`]: true },
// (optional) A rule set can extend another rule set
extends: ["@typespec/best-practices/recommended"],
// (optional) A rule set can extend another rule set
extends: ["@typespec/best-practices/recommended"],
// (optional) A rule set can disable a rule enabled in a ruleset it extended.
disable: {
"`@typespec/best-practices/no-a": "This doesn't apply in this ruleset.",
},
// (optional) A rule set can disable a rule enabled in a ruleset it extended.
disable: {
"`@typespec/best-practices/no-a": "This doesn't apply in this ruleset.",
},
},
},

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

@ -11,6 +11,7 @@ export {
createCadlLibrary,
createLinterRule as createRule,
createTypeSpecLibrary,
defineLinter,
paramMessage,
// eslint-disable-next-line deprecation/deprecation
setCadlNamespace,

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

@ -6,7 +6,9 @@ import {
CallableMessage,
DiagnosticMessages,
JSONSchemaValidator,
LinterDefinition,
LinterRuleDefinition,
StateDef,
TypeSpecLibrary,
TypeSpecLibraryDef,
} from "./types.js";
@ -28,6 +30,18 @@ export function getLibraryUrlsLoaded(): Set<string> {
/** @deprecated use createTypeSpecLibrary */
export const createCadlLibrary = createTypeSpecLibrary;
function createStateKeys<T extends string>(
libName: string,
state: Record<T, StateDef> | undefined
): Record<T, symbol> {
const result: Record<string, symbol> = {};
for (const key of Object.keys(state ?? {})) {
result[key] = Symbol.for(`${libName}/${key}`);
}
return result as Record<T, symbol>;
}
/**
* Create a new TypeSpec library definition.
* @param lib Library definition.
@ -49,10 +63,12 @@ export const createCadlLibrary = createTypeSpecLibrary;
export function createTypeSpecLibrary<
T extends { [code: string]: DiagnosticMessages },
E extends Record<string, any>,
>(lib: Readonly<TypeSpecLibraryDef<T, E>>): TypeSpecLibrary<T, E> {
State extends string = never,
>(lib: Readonly<TypeSpecLibraryDef<T, E, State>>): TypeSpecLibrary<T, E, State> {
let emitterOptionValidator: JSONSchemaValidator;
const { reportDiagnostic, createDiagnostic } = createDiagnosticCreator(lib.diagnostics, lib.name);
function createStateSymbol(name: string): symbol {
return Symbol.for(`${lib.name}.${name}`);
}
@ -61,8 +77,11 @@ export function createTypeSpecLibrary<
if (caller) {
loadedUrls.add(caller);
}
return {
...lib,
diagnostics: lib.diagnostics,
stateKeys: createStateKeys(lib.name, lib.state),
reportDiagnostic,
createDiagnostic,
createStateSymbol,
@ -82,6 +101,10 @@ export function createTypeSpecLibrary<
}
}
export function defineLinter(def: LinterDefinition): LinterDefinition {
return def;
}
export function paramMessage<T extends string[]>(
strings: readonly string[],
...keys: T

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

@ -7,6 +7,7 @@ import {
Diagnostic,
DiagnosticMessages,
LibraryInstance,
LinterDefinition,
LinterRule,
LinterRuleContext,
LinterRuleDiagnosticReport,
@ -36,6 +37,11 @@ export function createLinter(
lint,
};
function getLinterDefinition(library: LibraryInstance): LinterDefinition | undefined {
// eslint-disable-next-line deprecation/deprecation
return library?.linter ?? library?.definition?.linter;
}
async function extendRuleSet(ruleSet: LinterRuleSet): Promise<readonly Diagnostic[]> {
tracer.trace("extend-rule-set.start", JSON.stringify(ruleSet, null, 2));
const diagnostics = createDiagnosticCollector();
@ -44,7 +50,8 @@ export function createLinter(
const ref = diagnostics.pipe(parseRuleReference(extendingRuleSetName));
if (ref) {
const library = await resolveLibrary(ref.libraryName);
const extendingRuleSet = library?.definition?.linter?.ruleSets?.[ref.name];
const libLinterDefinition = library && getLinterDefinition(library);
const extendingRuleSet = libLinterDefinition?.ruleSets?.[ref.name];
if (extendingRuleSet) {
await extendRuleSet(extendingRuleSet);
} else {
@ -139,8 +146,9 @@ export function createLinter(
tracer.trace("register-library", name);
const library = await loadLibrary(name);
if (library?.definition?.linter?.rules) {
for (const ruleDef of library.definition.linter.rules) {
const linter = library && getLinterDefinition(library);
if (linter?.rules) {
for (const ruleDef of linter.rules) {
const ruleId = `${name}/${ruleDef.name}`;
tracer.trace(
"register-library.rule",

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

@ -710,6 +710,7 @@ export async function compile(
...resolution,
metadata,
definition: libDefinition,
linter: entrypoint?.esmExports.$linter,
};
}

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

@ -1783,6 +1783,7 @@ export interface LibraryInstance {
entrypoint: JsSourceFileNode | undefined;
metadata: LibraryMetadata;
definition?: TypeSpecLibrary<any>;
linter: LinterDefinition;
}
export type LibraryMetadata = FileLibraryMetadata | ModuleLibraryMetadata;
@ -2068,18 +2069,22 @@ export type CadlLibraryDef<
E extends Record<string, any> = Record<string, never>,
> = TypeSpecLibraryDef<T, E>;
/**
* Definition of a TypeSpec library
*/
export interface StateDef {
/**
* Description for this state.
*/
readonly description?: string;
}
export interface TypeSpecLibraryDef<
T extends { [code: string]: DiagnosticMessages },
E extends Record<string, any> = Record<string, never>,
State extends string = never,
> {
/**
* Name of the library. Must match the package.json name.
* Library name. MUST match package.json name.
*/
readonly name: string;
/**
* Map of potential diagnostics that can be emitted in this library where the key is the diagnostic code.
*/
@ -2100,8 +2105,11 @@ export interface TypeSpecLibraryDef<
/**
* Configuration if library is providing linting rules/rulesets.
* @deprecated Use `export const $linter` instead. This will cause circular reference with linters.
*/
readonly linter?: LinterDefinition;
readonly state?: Record<State, StateDef>;
}
export interface LinterDefinition {
@ -2177,7 +2185,11 @@ export type CadlLibrary<
export interface TypeSpecLibrary<
T extends { [code: string]: DiagnosticMessages },
E extends Record<string, any> = Record<string, never>,
> extends TypeSpecLibraryDef<T, E> {
State extends string = never,
> extends TypeSpecLibraryDef<T, E, State> {
/** Library name */
readonly name: string;
/**
* JSON Schema validator for emitter options
* @internal
@ -2203,6 +2215,8 @@ export interface TypeSpecLibrary<
* All trace area logged via this tracer will be prefixed with the library name.
*/
getTracer(program: Program): Tracer;
readonly stateKeys: Record<State, symbol>;
}
/**

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

@ -48,8 +48,8 @@ describe("compiler: linter", () => {
definition: createTypeSpecLibrary({
name: "@typespec/test",
diagnostics: {},
linter: linterDef,
}),
linter: linterDef,
};
await host.compile("main.tsp");

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

@ -1,5 +1,3 @@
import { createDiagnostic, reportDiagnostic } from "./lib.js";
import {
DecoratorContext,
Diagnostic,
@ -23,8 +21,8 @@ import {
validateDecoratorTarget,
validateDecoratorUniqueOnNode,
} from "@typespec/compiler";
import { HttpStateKeys, createDiagnostic, reportDiagnostic } from "./lib.js";
import { setRoute, setSharedRoute } from "./route.js";
import { HttpStateKeys } from "./state.js";
import { getStatusCodesFromType } from "./status-codes.js";
import {
AuthenticationOption,
@ -85,11 +83,11 @@ export function $header(
target: context.decoratorTarget,
});
}
context.program.stateMap(HttpStateKeys.headerFieldsKey).set(entity, options);
context.program.stateMap(HttpStateKeys.header).set(entity, options);
}
export function getHeaderFieldOptions(program: Program, entity: Type): HeaderFieldOptions {
return program.stateMap(HttpStateKeys.headerFieldsKey).get(entity);
return program.stateMap(HttpStateKeys.header).get(entity);
}
export function getHeaderFieldName(program: Program, entity: Type): string {
@ -97,7 +95,7 @@ export function getHeaderFieldName(program: Program, entity: Type): string {
}
export function isHeader(program: Program, entity: Type) {
return program.stateMap(HttpStateKeys.headerFieldsKey).has(entity);
return program.stateMap(HttpStateKeys.header).has(entity);
}
export function $query(
@ -133,11 +131,11 @@ export function $query(
target: context.decoratorTarget,
});
}
context.program.stateMap(HttpStateKeys.queryFieldsKey).set(entity, options);
context.program.stateMap(HttpStateKeys.query).set(entity, options);
}
export function getQueryParamOptions(program: Program, entity: Type): QueryParameterOptions {
return program.stateMap(HttpStateKeys.queryFieldsKey).get(entity);
return program.stateMap(HttpStateKeys.query).get(entity);
}
export function getQueryParamName(program: Program, entity: Type): string {
@ -145,7 +143,7 @@ export function getQueryParamName(program: Program, entity: Type): string {
}
export function isQueryParam(program: Program, entity: Type) {
return program.stateMap(HttpStateKeys.queryFieldsKey).has(entity);
return program.stateMap(HttpStateKeys.query).has(entity);
}
export function $path(context: DecoratorContext, entity: ModelProperty, paramName?: string) {
@ -153,11 +151,11 @@ export function $path(context: DecoratorContext, entity: ModelProperty, paramNam
type: "path",
name: paramName ?? entity.name,
};
context.program.stateMap(HttpStateKeys.pathFieldsKey).set(entity, options);
context.program.stateMap(HttpStateKeys.path).set(entity, options);
}
export function getPathParamOptions(program: Program, entity: Type): PathParameterOptions {
return program.stateMap(HttpStateKeys.pathFieldsKey).get(entity);
return program.stateMap(HttpStateKeys.path).get(entity);
}
export function getPathParamName(program: Program, entity: Type): string {
@ -165,19 +163,19 @@ export function getPathParamName(program: Program, entity: Type): string {
}
export function isPathParam(program: Program, entity: Type) {
return program.stateMap(HttpStateKeys.pathFieldsKey).has(entity);
return program.stateMap(HttpStateKeys.path).has(entity);
}
export function $body(context: DecoratorContext, entity: ModelProperty) {
context.program.stateSet(HttpStateKeys.bodyFieldsKey).add(entity);
context.program.stateSet(HttpStateKeys.body).add(entity);
}
export function isBody(program: Program, entity: Type): boolean {
return program.stateSet(HttpStateKeys.bodyFieldsKey).has(entity);
return program.stateSet(HttpStateKeys.body).has(entity);
}
export function $statusCode(context: DecoratorContext, entity: ModelProperty) {
context.program.stateSet(HttpStateKeys.statusCodeKey).add(entity);
context.program.stateSet(HttpStateKeys.statusCode).add(entity);
// eslint-disable-next-line deprecation/deprecation
setLegacyStatusCodeState(context, entity);
@ -226,18 +224,18 @@ function setLegacyStatusCodeState(context: DecoratorContext, entity: ModelProper
});
return false;
}
context.program.stateMap(HttpStateKeys.statusCodeKey).set(entity, codes);
context.program.stateMap(HttpStateKeys.statusCode).set(entity, codes);
}
/**
* @deprecated DO NOT USE, for internal use only.
*/
export function setStatusCode(program: Program, entity: Model | ModelProperty, codes: string[]) {
program.stateMap(HttpStateKeys.statusCodeKey).set(entity, codes);
program.stateMap(HttpStateKeys.statusCode).set(entity, codes);
}
export function isStatusCode(program: Program, entity: Type) {
return program.stateMap(HttpStateKeys.statusCodeKey).has(entity);
return program.stateMap(HttpStateKeys.statusCode).has(entity);
}
export function getStatusCodesWithDiagnostics(
@ -306,8 +304,8 @@ function rangeDescription(start: number, end: number) {
function setOperationVerb(program: Program, entity: Type, verb: HttpVerb): void {
if (entity.kind === "Operation") {
if (!program.stateMap(HttpStateKeys.operationVerbsKey).has(entity)) {
program.stateMap(HttpStateKeys.operationVerbsKey).set(entity, verb);
if (!program.stateMap(HttpStateKeys.verbs).has(entity)) {
program.stateMap(HttpStateKeys.verbs).set(entity, verb);
} else {
reportDiagnostic(program, {
code: "http-verb-duplicate",
@ -325,7 +323,7 @@ function setOperationVerb(program: Program, entity: Type, verb: HttpVerb): void
}
export function getOperationVerb(program: Program, entity: Type): HttpVerb | undefined {
return program.stateMap(HttpStateKeys.operationVerbsKey).get(entity);
return program.stateMap(HttpStateKeys.verbs).get(entity);
}
export function $get(context: DecoratorContext, entity: Operation) {
@ -386,10 +384,10 @@ export function $server(
}
}
let servers: HttpServer[] = context.program.stateMap(HttpStateKeys.serversKey).get(target);
let servers: HttpServer[] = context.program.stateMap(HttpStateKeys.servers).get(target);
if (servers === undefined) {
servers = [];
context.program.stateMap(HttpStateKeys.serversKey).set(target, servers);
context.program.stateMap(HttpStateKeys.servers).set(target, servers);
}
servers.push({
url,
@ -399,7 +397,7 @@ export function $server(
}
export function getServers(program: Program, type: Namespace): HttpServer[] | undefined {
return program.stateMap(HttpStateKeys.serversKey).get(type);
return program.stateMap(HttpStateKeys.servers).get(type);
}
export function $plainData(context: DecoratorContext, entity: Model) {
@ -407,11 +405,11 @@ export function $plainData(context: DecoratorContext, entity: Model) {
const decoratorsToRemove = ["$header", "$body", "$query", "$path", "$statusCode"];
const [headers, bodies, queries, paths, statusCodes] = [
program.stateMap(HttpStateKeys.headerFieldsKey),
program.stateSet(HttpStateKeys.bodyFieldsKey),
program.stateMap(HttpStateKeys.queryFieldsKey),
program.stateMap(HttpStateKeys.pathFieldsKey),
program.stateMap(HttpStateKeys.statusCodeKey),
program.stateMap(HttpStateKeys.header),
program.stateSet(HttpStateKeys.body),
program.stateMap(HttpStateKeys.query),
program.stateMap(HttpStateKeys.path),
program.stateMap(HttpStateKeys.statusCode),
];
for (const property of entity.properties.values()) {
@ -449,7 +447,7 @@ export function setAuthentication(
serviceNamespace: Namespace,
auth: ServiceAuthentication
) {
program.stateMap(HttpStateKeys.authenticationKey).set(serviceNamespace, auth);
program.stateMap(HttpStateKeys.authentication).set(serviceNamespace, auth);
}
function extractServiceAuthentication(
@ -582,7 +580,7 @@ export function getAuthentication(
program: Program,
namespace: Namespace
): ServiceAuthentication | undefined {
return program.stateMap(HttpStateKeys.authenticationKey).get(namespace);
return program.stateMap(HttpStateKeys.authentication).get(namespace);
}
/**
@ -660,7 +658,7 @@ export function $includeInapplicableMetadataInPayload(
) {
return;
}
const state = context.program.stateMap(HttpStateKeys.includeInapplicableMetadataInPayloadKey);
const state = context.program.stateMap(HttpStateKeys.includeInapplicableMetadataInPayload);
state.set(entity, value);
}
@ -677,7 +675,7 @@ export function includeInapplicableMetadataInPayload(
): boolean {
let e: ModelProperty | Namespace | Model | undefined;
for (e = property; e; e = e.kind === "ModelProperty" ? e.model : e.namespace) {
const value = program.stateMap(HttpStateKeys.includeInapplicableMetadataInPayloadKey).get(e);
const value = program.stateMap(HttpStateKeys.includeInapplicableMetadataInPayload).get(e);
if (value !== undefined) {
return value;
}

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

@ -1,4 +1,5 @@
export { $lib } from "./lib.js";
export { $linter } from "./linter.js";
export * from "./content-types.js";
export * from "./decorators.js";

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

@ -1,5 +1,4 @@
import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
import { opReferenceContainerRouteRule } from "./rules/op-reference-container-route.js";
export const $lib = createTypeSpecLibrary({
name: "@typespec/http",
@ -131,16 +130,26 @@ export const $lib = createTypeSpecLibrary({
},
},
},
linter: {
rules: [opReferenceContainerRouteRule],
ruleSets: {
all: {
enable: {
[`@typespec/http/${opReferenceContainerRouteRule.name}`]: true,
},
},
state: {
authentication: { description: "State for the @auth decorator" },
header: { description: "State for the @header decorator" },
query: { description: "State for the @query decorator" },
path: { description: "State for the @path decorator" },
body: { description: "State for the @body decorator" },
statusCode: { description: "State for the @statusCode decorator" },
verbs: { description: "State for the verb decorators (@get, @post, @put, etc.)" },
servers: { description: "State for the @server decorator" },
includeInapplicableMetadataInPayload: {
description: "State for the @includeInapplicableMetadataInPayload decorator",
},
},
});
export const { reportDiagnostic, createDiagnostic, createStateSymbol } = $lib;
// route.ts
externalInterfaces: {},
routeProducer: {},
routes: {},
sharedRoutes: { description: "State for the @sharedRoute decorator" },
routeOptions: {},
},
} as const);
export const { reportDiagnostic, createDiagnostic, stateKeys: HttpStateKeys } = $lib;

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

@ -0,0 +1,13 @@
import { defineLinter } from "@typespec/compiler";
import { opReferenceContainerRouteRule } from "./rules/op-reference-container-route.js";
export const $linter = defineLinter({
rules: [opReferenceContainerRouteRule],
ruleSets: {
all: {
enable: {
[`@typespec/http/${opReferenceContainerRouteRule.name}`]: true,
},
},
},
});

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

@ -25,9 +25,8 @@ import {
isHeader,
isStatusCode,
} from "./decorators.js";
import { createDiagnostic, reportDiagnostic } from "./lib.js";
import { createDiagnostic, HttpStateKeys, reportDiagnostic } from "./lib.js";
import { gatherMetadata, isApplicableMetadata, Visibility } from "./metadata.js";
import { HttpStateKeys } from "./state.js";
import { HttpOperationResponse, HttpStatusCodes, HttpStatusCodesEntry } from "./types.js";
/**
@ -189,7 +188,7 @@ function getResponseStatusCodes(
}
function getExplicitSetStatusCode(program: Program, entity: Model | ModelProperty): "*"[] {
return program.stateMap(HttpStateKeys.statusCodeKey).get(entity) ?? [];
return program.stateMap(HttpStateKeys.statusCode).get(entity) ?? [];
}
/**

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

@ -1,7 +1,4 @@
import { createDiagnostic, reportDiagnostic } from "./lib.js";
import {
createDiagnosticCollector,
DecoratorContext,
DiagnosticResult,
Interface,
@ -9,10 +6,11 @@ import {
Operation,
Program,
Type,
createDiagnosticCollector,
validateDecoratorTarget,
} from "@typespec/compiler";
import { HttpStateKeys, createDiagnostic, reportDiagnostic } from "./lib.js";
import { getOperationParameters } from "./parameters.js";
import { HttpStateKeys } from "./state.js";
import {
HttpOperation,
HttpOperationParameters,
@ -196,11 +194,11 @@ export function setRouteProducer(
operation: Operation,
routeProducer: RouteProducer
): void {
program.stateMap(HttpStateKeys.routeProducerKey).set(operation, routeProducer);
program.stateMap(HttpStateKeys.routeProducer).set(operation, routeProducer);
}
export function getRouteProducer(program: Program, operation: Operation): RouteProducer {
return program.stateMap(HttpStateKeys.routeProducerKey).get(operation);
return program.stateMap(HttpStateKeys.routeProducer).get(operation);
}
export function setRoute(context: DecoratorContext, entity: Type, details: RoutePath) {
@ -210,7 +208,7 @@ export function setRoute(context: DecoratorContext, entity: Type, details: Route
return;
}
const state = context.program.stateMap(HttpStateKeys.routesKey);
const state = context.program.stateMap(HttpStateKeys.routes);
if (state.has(entity) && entity.kind === "Namespace") {
const existingPath: string | undefined = state.get(entity);
@ -230,18 +228,18 @@ export function setRoute(context: DecoratorContext, entity: Type, details: Route
}
export function setSharedRoute(program: Program, operation: Operation) {
program.stateMap(HttpStateKeys.sharedRoutesKey).set(operation, true);
program.stateMap(HttpStateKeys.sharedRoutes).set(operation, true);
}
export function isSharedRoute(program: Program, operation: Operation): boolean {
return program.stateMap(HttpStateKeys.sharedRoutesKey).get(operation) === true;
return program.stateMap(HttpStateKeys.sharedRoutes).get(operation) === true;
}
export function getRoutePath(
program: Program,
entity: Namespace | Interface | Operation
): RoutePath | undefined {
const path = program.stateMap(HttpStateKeys.routesKey).get(entity);
const path = program.stateMap(HttpStateKeys.routes).get(entity);
return path
? {
path,
@ -255,12 +253,12 @@ export function setRouteOptionsForNamespace(
namespace: Namespace,
options: RouteOptions
) {
program.stateMap(HttpStateKeys.routeOptionsKey).set(namespace, options);
program.stateMap(HttpStateKeys.routeOptions).set(namespace, options);
}
export function getRouteOptionsForNamespace(
program: Program,
namespace: Namespace
): RouteOptions | undefined {
return program.stateMap(HttpStateKeys.routeOptionsKey).get(namespace);
return program.stateMap(HttpStateKeys.routeOptions).get(namespace);
}

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

@ -1,28 +0,0 @@
// FIXME - This is a workaround for the circular dependency issue when loading
// createStateSymbol.
// Issue: https://github.com/microsoft/typespec/issues/2301
function httpCreateStateSymbol(name: string): symbol {
return Symbol.for(`@typespec/http.${name}`);
}
export const HttpStateKeys = {
// decorators.ts
authenticationKey: httpCreateStateSymbol("authentication"),
headerFieldsKey: httpCreateStateSymbol("header"),
queryFieldsKey: httpCreateStateSymbol("query"),
pathFieldsKey: httpCreateStateSymbol("path"),
bodyFieldsKey: httpCreateStateSymbol("body"),
statusCodeKey: httpCreateStateSymbol("statusCode"),
operationVerbsKey: httpCreateStateSymbol("verbs"),
serversKey: httpCreateStateSymbol("servers"),
includeInapplicableMetadataInPayloadKey: httpCreateStateSymbol(
"includeInapplicableMetadataInPayload"
),
// route.ts
externalInterfaces: httpCreateStateSymbol("externalInterfaces"),
routeProducerKey: httpCreateStateSymbol("routeProducer"),
routesKey: httpCreateStateSymbol("routes"),
sharedRoutesKey: httpCreateStateSymbol("sharedRoutes"),
routeOptionsKey: httpCreateStateSymbol("routeOptions"),
};

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

@ -21,12 +21,16 @@ export async function createBrowserHost(
const libraries: Record<string, PlaygroundTspLibrary> = {};
for (const libName of libsToLoad) {
const { _TypeSpecLibrary_, $lib } = (await importLibrary(libName, importOptions)) as any;
const { _TypeSpecLibrary_, $lib, $linter } = (await importLibrary(
libName,
importOptions
)) as any;
libraries[libName] = {
name: libName,
isEmitter: $lib?.emitter,
definition: $lib,
packageJson: JSON.parse(_TypeSpecLibrary_.typespecSourceFiles["package.json"]),
linter: $linter,
};
for (const [key, value] of Object.entries<any>(_TypeSpecLibrary_.typespecSourceFiles)) {
virtualFs.set(`/test/node_modules/${libName}/${key}`, value);

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

@ -15,9 +15,9 @@ export const LinterForm: FunctionComponent<LinterFormProps> = ({
onLinterRuleSetChanged,
}) => {
const rulesets = Object.values(libraries).flatMap((lib) => {
return Object.keys(lib.definition?.linter?.ruleSets ?? {}).map(
(x) => `${lib.name}/${x}`
) as RuleRef[];
// eslint-disable-next-line deprecation/deprecation
const linter = lib.linter ?? lib.definition?.linter;
return Object.keys(linter?.ruleSets ?? {}).map((x) => `${lib.name}/${x}`) as RuleRef[];
});
if (rulesets.length === 0) {
return <>No ruleset available</>;

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

@ -1,4 +1,10 @@
import { CompilerHost, CompilerOptions, NodePackage, TypeSpecLibrary } from "@typespec/compiler";
import {
CompilerHost,
CompilerOptions,
LinterDefinition,
NodePackage,
TypeSpecLibrary,
} from "@typespec/compiler";
export interface PlaygroundSample {
filename: string;
@ -16,6 +22,7 @@ export interface PlaygroundTspLibrary {
packageJson: NodePackage;
isEmitter: boolean;
definition?: TypeSpecLibrary<any>;
linter?: LinterDefinition;
}
export interface BrowserHost extends CompilerHost {

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

@ -89,8 +89,10 @@ export async function extractLibraryRefDocs(
options: extractEmitterOptionsRefDoc(lib.emitter.options),
};
}
if (lib?.linter) {
refDoc.linter = extractLinterRefDoc(lib.name, lib.linter);
// eslint-disable-next-line deprecation/deprecation
const linter = entrypoint.$linter ?? lib?.linter;
if (lib && linter) {
refDoc.linter = extractLinterRefDoc(lib.name, linter);
}
}