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:
Родитель
204083faa0
Коммит
da99aa955b
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче