From 3eed29834600d02c6a1ab3dd039188edf0fb2951 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Tue, 17 Sep 2024 16:14:01 -0700 Subject: [PATCH] Use reusable state accessor for compiler decorators (#4465) Idea that could be expanded/exposed to other libraries in the future. Define clearly how to access a certain state key. Doing that now as for paging there is a lot of decorators that are added and that will simplify. --- ...actor-state-accessor-2024-8-17-15-11-51.md | 7 + ...actor-state-accessor-2024-8-17-21-56-42.md | 8 + packages/compiler/package.json | 4 + packages/compiler/src/experimental/index.ts | 1 + .../src/experimental/state-accessor.ts | 30 +++ packages/compiler/src/lib/decorators.ts | 231 +++++++++--------- packages/compiler/src/lib/encoded-names.ts | 18 +- packages/compiler/src/lib/service.ts | 28 +-- packages/compiler/src/lib/utils.ts | 14 ++ packages/http-server-csharp/src/utils.ts | 4 +- tsconfig.ws.json | 3 +- 11 files changed, 200 insertions(+), 148 deletions(-) create mode 100644 .chronus/changes/refactor-state-accessor-2024-8-17-15-11-51.md create mode 100644 .chronus/changes/refactor-state-accessor-2024-8-17-21-56-42.md create mode 100644 packages/compiler/src/experimental/index.ts create mode 100644 packages/compiler/src/experimental/state-accessor.ts create mode 100644 packages/compiler/src/lib/utils.ts diff --git a/.chronus/changes/refactor-state-accessor-2024-8-17-15-11-51.md b/.chronus/changes/refactor-state-accessor-2024-8-17-15-11-51.md new file mode 100644 index 00000000000..0c90c5c0448 --- /dev/null +++ b/.chronus/changes/refactor-state-accessor-2024-8-17-15-11-51.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-server-csharp" +--- + +Fix potential undefined \ No newline at end of file diff --git a/.chronus/changes/refactor-state-accessor-2024-8-17-21-56-42.md b/.chronus/changes/refactor-state-accessor-2024-8-17-21-56-42.md new file mode 100644 index 00000000000..ab42b7b40b4 --- /dev/null +++ b/.chronus/changes/refactor-state-accessor-2024-8-17-21-56-42.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: internal +packages: + - "@typespec/compiler" +--- + +Use reusable state accessor for compiler decorators diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 098b4b377d0..1ea85038632 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -40,6 +40,10 @@ "./emitter-framework": { "types": "./dist/src/emitter-framework/index.d.ts", "default": "./dist/src/emitter-framework/index.js" + }, + "./experimental": { + "types": "./dist/src/experimental/index.d.ts", + "default": "./dist/src/experimental/index.js" } }, "browser": { diff --git a/packages/compiler/src/experimental/index.ts b/packages/compiler/src/experimental/index.ts new file mode 100644 index 00000000000..5dd6e5163cf --- /dev/null +++ b/packages/compiler/src/experimental/index.ts @@ -0,0 +1 @@ +export { unsafe_useStateMap, unsafe_useStateSet } from "./state-accessor.js"; diff --git a/packages/compiler/src/experimental/state-accessor.ts b/packages/compiler/src/experimental/state-accessor.ts new file mode 100644 index 00000000000..7ae586342f4 --- /dev/null +++ b/packages/compiler/src/experimental/state-accessor.ts @@ -0,0 +1,30 @@ +import type { Program } from "../core/program.js"; +import type { Type } from "../core/types.js"; + +type StateMapGetter = (program: Program, type: K) => V | undefined; +type StateMapSetter = (program: Program, type: K, value: V) => void; +type StateMapMapGetter = (program: Program) => Map; + +/** @experimental */ +export function unsafe_useStateMap( + key: symbol, +): [StateMapGetter, StateMapSetter, StateMapMapGetter] { + const getter = (program: Program, target: K) => program.stateMap(key).get(target); + const setter = (program: Program, target: K, value: V) => + program.stateMap(key).set(target, value); + const mapGetter = (program: Program) => program.stateMap(key); + return [getter, setter, mapGetter as any]; +} + +type StateSetGetter = (program: Program, type: K) => boolean; +type StateSetSetter = (program: Program, type: K) => void; + +/** @experimental */ +export function unsafe_useStateSet( + key: symbol, +): [StateSetGetter, StateSetSetter] { + const getter = (program: Program, target: K) => program.stateSet(key).has(target); + const setter = (program: Program, target: K) => program.stateSet(key).add(target); + + return [getter, setter]; +} diff --git a/packages/compiler/src/lib/decorators.ts b/packages/compiler/src/lib/decorators.ts index b04f7157db9..c3524783124 100644 --- a/packages/compiler/src/lib/decorators.ts +++ b/packages/compiler/src/lib/decorators.ts @@ -106,6 +106,7 @@ import { UnionVariant, Value, } from "../core/types.js"; +import { useStateMap, useStateSet } from "./utils.js"; export { $encodedName, resolveEncodedName } from "./encoded-names.js"; export { serializeValueAsJson } from "./examples.js"; @@ -129,7 +130,7 @@ function createStateSymbol(name: string) { return Symbol.for(`TypeSpec.${name}`); } -const summaryKey = createStateSymbol("summary"); +const [getSummary, setSummary] = useStateMap("summary"); /** * @summary attaches a documentation string. It is typically used to give a short, single-line * description, and can be used in combination with or instead of @doc. @@ -149,12 +150,10 @@ export const $summary: SummaryDecorator = ( text = replaceTemplatedStringFromProperties(text, sourceObject); } - context.program.stateMap(summaryKey).set(target, text); + setSummary(context.program, target, text); }; -export function getSummary(program: Program, type: Type): string | undefined { - return program.stateMap(summaryKey).get(type); -} +export { getSummary }; /** * @doc attaches a documentation string. Works great with multi-line string literals. @@ -332,15 +331,14 @@ function validateTargetingAString( // -- @error decorator ---------------------- -const errorKey = createStateSymbol("error"); - +const [getErrorState, setErrorState] = useStateSet("error"); /** * `@error` decorator marks a model as an error type. * Any derived models (using extends) will also be seen as error types. */ export const $error: ErrorDecorator = (context: DecoratorContext, entity: Model) => { validateDecoratorUniqueOnNode(context, entity, $error); - context.program.stateSet(errorKey).add(entity); + setErrorState(context.program, entity); }; /** @@ -352,7 +350,7 @@ export function isErrorModel(program: Program, target: Type): boolean { } let current: Model | undefined = target; while (current) { - if (program.stateSet(errorKey).has(current)) { + if (getErrorState(program, current)) { return true; } current = current.baseModel; @@ -362,7 +360,7 @@ export function isErrorModel(program: Program, target: Type): boolean { // -- @format decorator --------------------- -const formatValuesKey = createStateSymbol("formatValues"); +const [getFormat, setFormat] = useStateMap("format"); /** * `@format` - specify the data format hint for a string type @@ -396,16 +394,13 @@ export const $format: FormatDecorator = ( ); } - context.program.stateMap(formatValuesKey).set(target, format); + setFormat(context.program, target, format); }; -export function getFormat(program: Program, target: Type): string | undefined { - return program.stateMap(formatValuesKey).get(target); -} +export { getFormat }; // -- @pattern decorator --------------------- - -const patternValuesKey = createStateSymbol("patternValues"); +const [getPatternData, setPatternData] = useStateMap("patternValues"); export interface PatternData { readonly pattern: string; @@ -429,7 +424,7 @@ export const $pattern: PatternDecorator = ( validationMessage, }; - context.program.stateMap(patternValuesKey).set(target, patternData); + setPatternData(context.program, target, patternData); }; /** @@ -445,17 +440,17 @@ export function getPattern(program: Program, target: Type): string | undefined { return getPatternData(program, target)?.pattern; } -/** - * Gets the associated pattern data, including the pattern regular expression and optional validation message, if any - * has been set. - * - * @param program - the Program containing the target Type - * @param target - the type to get the pattern data for - * @returns the pattern data, if any was set - */ -export function getPatternData(program: Program, target: Type): PatternData | undefined { - return program.stateMap(patternValuesKey).get(target); -} +export { + /** + * Gets the associated pattern data, including the pattern regular expression and optional validation message, if any + * has been set. + * + * @param program - the Program containing the target Type + * @param target - the type to get the pattern data for + * @returns the pattern data, if any was set + */ + getPatternData, +}; // -- @minLength decorator --------------------- @@ -658,7 +653,7 @@ export const $maxValueExclusive: MaxValueExclusiveDecorator = ( }; // -- @secret decorator --------------------- -const secretTypesKey = createStateSymbol("secretTypes"); +const [isSecret, markSecret] = useStateSet("secretTypes"); /** * Mark a string as a secret value that should be treated carefully to avoid exposure @@ -674,12 +669,10 @@ export const $secret: SecretDecorator = ( if (!validateTargetingAString(context, target, "@secret")) { return; } - context.program.stateMap(secretTypesKey).set(target, true); + markSecret(context.program, target); }; -export function isSecret(program: Program, target: Type): boolean | undefined { - return program.stateMap(secretTypesKey).get(target); -} +export { isSecret }; export type DateTimeKnownEncoding = "rfc3339" | "rfc7231" | "unixTimestamp"; export type DurationKnownEncoding = "ISO8601" | "seconds"; @@ -694,7 +687,7 @@ export interface EncodeData { type: Scalar; } -const encodeKey = createStateSymbol("encode"); +const [getEncode, setEncodeData] = useStateMap("encode"); export const $encode: EncodeDecorator = ( context: DecoratorContext, target: Scalar | ModelProperty, @@ -709,7 +702,7 @@ export const $encode: EncodeDecorator = ( } const targetType = getPropertyType(target); validateEncodeData(context, targetType, encodeData); - context.program.stateMap(encodeKey).set(target, encodeData); + setEncodeData(context.program, target, encodeData); }; function computeEncoding( @@ -812,17 +805,13 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData: } } -export function getEncode( - program: Program, - target: Scalar | ModelProperty, -): EncodeData | undefined { - return program.stateMap(encodeKey).get(target); -} +export { getEncode }; // -- @visibility decorator --------------------- -const visibilitySettingsKey = createStateSymbol("visibilitySettings"); - +const [getVisibility, setVisibility, getVisibilityStateMap] = useStateMap( + "visibilitySettings", +); export const $visibility: VisibilityDecorator = ( context: DecoratorContext, target: ModelProperty, @@ -830,15 +819,13 @@ export const $visibility: VisibilityDecorator = ( ) => { validateDecoratorUniqueOnNode(context, target, $visibility); - context.program.stateMap(visibilitySettingsKey).set(target, visibilities); + setVisibility(context.program, target, visibilities); }; -export function getVisibility(program: Program, target: Type): string[] | undefined { - return program.stateMap(visibilitySettingsKey).get(target); -} +export { getVisibility }; function clearVisibilities(program: Program, target: Type) { - program.stateMap(visibilitySettingsKey).delete(target); + getVisibilityStateMap(program).delete(target); } export const $withVisibility: WithVisibilityDecorator = ( @@ -993,7 +980,8 @@ export function isListOperation(program: Program, target: Operation): boolean { } // -- @tag decorator --------------------- -const tagPropertiesKey = createStateSymbol("tagProperties"); + +const [getTagsState, setTags] = useStateMap("tagProperties"); // Set a tag on an operation, interface, or namespace. There can be multiple tags on an // operation, interface, or namespace. @@ -1002,17 +990,17 @@ export const $tag: TagDecorator = ( target: Operation | Namespace | Interface, tag: string, ) => { - const tags = context.program.stateMap(tagPropertiesKey).get(target); + const tags = getTagsState(context.program, target); if (tags) { tags.push(tag); } else { - context.program.stateMap(tagPropertiesKey).set(target, [tag]); + setTags(context.program, target, [tag]); } }; // Return the tags set on an operation or namespace export function getTags(program: Program, target: Type): string[] { - return program.stateMap(tagPropertiesKey).get(target) || []; + return getTagsState(program, target) || []; } // Merge the tags for a operation with the tags that are on the namespace or @@ -1043,8 +1031,7 @@ export function getAllTags( // -- @friendlyName decorator --------------------- -const friendlyNamesKey = createStateSymbol("friendlyNames"); - +const [getFriendlyName, setFriendlyName] = useStateMap("friendlyNames"); export const $friendlyName: FriendlyNameDecorator = ( context: DecoratorContext, target: Type, @@ -1077,14 +1064,13 @@ export const $friendlyName: FriendlyNameDecorator = ( friendlyName = replaceTemplatedStringFromProperties(friendlyName, sourceObject); } - context.program.stateMap(friendlyNamesKey).set(target, friendlyName); + setFriendlyName(context.program, target, friendlyName); }; -export function getFriendlyName(program: Program, target: Type): string { - return program.stateMap(friendlyNamesKey).get(target); -} +export { getFriendlyName }; + +const [getKnownValues, setKnownValues] = useStateMap("knownValues"); -const knownValuesKey = createStateSymbol("knownValues"); /** * `@knownValues` marks a string type with an enum that contains all known values * @@ -1127,7 +1113,7 @@ export const $knownValues = ( return; } } - context.program.stateMap(knownValuesKey).set(target, knownValues); + setKnownValues(context.program, target, knownValues); }; function isEnumMemberAssignableToType(program: Program, typeName: Type, member: EnumMember) { @@ -1141,12 +1127,9 @@ function isEnumMemberAssignableToType(program: Program, typeName: Type, member: return false; } } +export { getKnownValues }; -export function getKnownValues(program: Program, target: Scalar | ModelProperty): Enum | undefined { - return program.stateMap(knownValuesKey).get(target); -} - -const keyKey = createStateSymbol("key"); +const [getKey, setKey] = useStateMap("key"); /** * `@key` - mark a model property as the key to identify instances of that type @@ -1173,15 +1156,15 @@ export const $key: KeyDecorator = ( } // Register the key property - context.program.stateMap(keyKey).set(entity, altName || entity.name); + setKey(context.program, entity, altName || entity.name); }; export function isKey(program: Program, property: ModelProperty) { - return program.stateMap(keyKey).has(property); + return getKey(program, property) !== undefined; } export function getKeyName(program: Program, property: ModelProperty): string { - return program.stateMap(keyKey).get(property); + return getKey(program, property) || property.name; } export const $withDefaultKeyVisibility: WithDefaultKeyVisibilityDecorator = ( @@ -1245,8 +1228,10 @@ export function getDeprecated(program: Program, type: Type): string | undefined return getDeprecationDetails(program, type)?.message; } -const overloadedByKey = createStateSymbol("overloadedByKey"); -const overloadsOperationKey = createStateSymbol("overloadsOperation"); +const [getOverloads, setOverloads] = useStateMap("overloadedByKey"); +const [getOverloadedOperation, setOverloadBase] = useStateMap( + "overloadsOperation", +); /** * `@overload` - Indicate that the target overloads (specializes) the overloads type. @@ -1281,9 +1266,10 @@ export const $overload: OverloadDecorator = ( }); } // Save the information about the overloaded operation - context.program.stateMap(overloadsOperationKey).set(target, overloadBase); + + setOverloadBase(context.program, target, overloadBase); const existingOverloads = getOverloads(context.program, overloadBase) || new Array(); - context.program.stateMap(overloadedByKey).set(overloadBase, existingOverloads.concat(target)); + setOverloads(context.program, overloadBase, existingOverloads.concat(target)); }; function areOperationsInSameContainer(op1: Operation, op2: Operation): boolean { @@ -1310,28 +1296,23 @@ function getBaseInterface(int1: Interface): Interface { : getBaseInterface(int1.projectionSource as Interface); } -/** - * Get all operations that are marked as overloads of the given operation - * @param program Program - * @param operation Operation - * @returns An array of operations that overload the given operation. - */ -export function getOverloads(program: Program, operation: Operation): Operation[] | undefined { - return program.stateMap(overloadedByKey).get(operation); -} +export { + /** + * If the given operation overloads another operation, return that operation. + * @param program Program + * @param operation The operation to check for an overload target. + * @returns The operation this operation overloads, if any. + */ + getOverloadedOperation, -/** - * If the given operation overloads another operation, return that operation. - * @param program Program - * @param operation The operation to check for an overload target. - * @returns The operation this operation overloads, if any. - */ -export function getOverloadedOperation( - program: Program, - operation: Operation, -): Operation | undefined { - return program.stateMap(overloadsOperationKey).get(operation); -} + /** + * Get all operations that are marked as overloads of the given operation + * @param program Program + * @param operation Operation + * @returns An array of operations that overload the given operation. + */ + getOverloads, +}; const projectedNameKey = createStateSymbol("projectedNameKey"); @@ -1429,7 +1410,9 @@ export const $discriminator: DiscriminatorDecorator = ( setDiscriminator(context.program, entity, discriminator); }; -const parameterVisibilityKey = createStateSymbol("parameterVisibility"); +const [getParameterVisibility, setParameterVisibility] = useStateMap( + "parameterVisibility", +); export const $parameterVisibility: ParameterVisibilityDecorator = ( context: DecoratorContext, @@ -1437,37 +1420,38 @@ export const $parameterVisibility: ParameterVisibilityDecorator = ( ...visibilities: string[] ) => { validateDecoratorUniqueOnNode(context, entity, $parameterVisibility); - context.program.stateMap(parameterVisibilityKey).set(entity, visibilities); + setParameterVisibility(context.program, entity, visibilities); }; -/** - * Returns the visibilities of the parameters of the given operation, if provided with `@parameterVisibility`. - * - * @see {@link $parameterVisibility} - */ -export function getParameterVisibility(program: Program, entity: Operation): string[] | undefined { - return program.stateMap(parameterVisibilityKey).get(entity); -} - -const returnTypeVisibilityKey = createStateSymbol("returnTypeVisibility"); +export { + /** + * Returns the visibilities of the parameters of the given operation, if provided with `@parameterVisibility`. + * + * @see {@link $parameterVisibility} + */ + getParameterVisibility, +}; +const [getReturnTypeVisibility, setReturnTypeVisibility] = useStateMap( + "returnTypeVisibility", +); export const $returnTypeVisibility: ReturnTypeVisibilityDecorator = ( context: DecoratorContext, entity: Operation, ...visibilities: string[] ) => { validateDecoratorUniqueOnNode(context, entity, $returnTypeVisibility); - context.program.stateMap(returnTypeVisibilityKey).set(entity, visibilities); + setReturnTypeVisibility(context.program, entity, visibilities); }; -/** - * Returns the visibilities of the return type of the given operation, if provided with `@returnTypeVisibility`. - * - * @see {@link $returnTypeVisibility} - */ -export function getReturnTypeVisibility(program: Program, entity: Operation): string[] | undefined { - return program.stateMap(returnTypeVisibilityKey).get(entity); -} +export { + /** + * Returns the visibilities of the return type of the given operation, if provided with `@returnTypeVisibility`. + * + * @see {@link $returnTypeVisibility} + */ + getReturnTypeVisibility, +}; export interface Example extends ExampleOptions { readonly value: Value; @@ -1477,7 +1461,10 @@ export interface OpExample extends ExampleOptions { readonly returnType?: Value; } -const exampleKey = createStateSymbol("examples"); +const [getExamplesState, setExamples] = useStateMap< + Model | Scalar | Enum | Union | ModelProperty | UnionVariant, + Example[] +>("examples"); export const $example: ExampleDecorator = ( context: DecoratorContext, target: Model | Scalar | Enum | Union | ModelProperty | UnionVariant, @@ -1503,10 +1490,10 @@ export const $example: ExampleDecorator = ( } } - let list = context.program.stateMap(exampleKey).get(target); + let list = getExamplesState(context.program, target); if (list === undefined) { list = []; - context.program.stateMap(exampleKey).set(target, list); + setExamples(context.program, target, list); } list.push({ value: rawExample, ...options }); }; @@ -1515,10 +1502,10 @@ export function getExamples( program: Program, target: Model | Scalar | Enum | Union | ModelProperty, ): readonly Example[] { - return program.stateMap(exampleKey).get(target) ?? []; + return getExamplesState(program, target) ?? []; } -const opExampleKey = createStateSymbol("opExamples"); +const [getOpExamplesState, setOpExamples] = useStateMap("opExamples"); export const $opExample: OpExampleDecorator = ( context: DecoratorContext, target: Operation, @@ -1559,10 +1546,10 @@ export const $opExample: OpExampleDecorator = ( } } - let list = context.program.stateMap(opExampleKey).get(target); + let list = getOpExamplesState(context.program, target); if (list === undefined) { list = []; - context.program.stateMap(opExampleKey).set(target, list); + setOpExamples(context.program, target, list); } list.push({ parameters, returnType, ...(options as any) }); }; @@ -1586,5 +1573,5 @@ function checkExampleValid( } export function getOpExamples(program: Program, target: Operation): OpExample[] { - return program.stateMap(opExampleKey).get(target) ?? []; + return getOpExamplesState(program, target) ?? []; } diff --git a/packages/compiler/src/lib/encoded-names.ts b/packages/compiler/src/lib/encoded-names.ts index 95aefe4af67..6156ed72eec 100644 --- a/packages/compiler/src/lib/encoded-names.ts +++ b/packages/compiler/src/lib/encoded-names.ts @@ -3,12 +3,12 @@ import { parseMimeType } from "../core/mime-type.js"; import type { Program } from "../core/program.js"; import type { DecoratorContext, Enum, Model, Type, Union } from "../core/types.js"; import { DuplicateTracker } from "../utils/index.js"; +import { useStateMap } from "./utils.js"; -function createStateSymbol(name: string) { - return Symbol.for(`TypeSpec.${name}`); -} - -const encodedNameKey = createStateSymbol("encodedName"); +const [getEncodedNamesMap, setEncodedNamesMap, getEncodedNamesStateMap] = useStateMap< + Type, + Map +>("encodedName"); export function $encodedName( context: DecoratorContext, @@ -16,10 +16,10 @@ export function $encodedName( mimeType: string, name: string, ) { - let existing = context.program.stateMap(encodedNameKey).get(target); + let existing = getEncodedNamesMap(context.program, target); if (existing === undefined) { existing = new Map(); - context.program.stateMap(encodedNameKey).set(target, existing); + setEncodedNamesMap(context.program, target, existing); } const mimeTypeObj = parseMimeType(mimeType); @@ -47,7 +47,7 @@ function getEncodedName(program: Program, target: Type, mimeType: string): strin const resolvedMimeType = mimeTypeObj?.suffix ? `${mimeTypeObj.type}/${mimeTypeObj.suffix}` : mimeType; - return program.stateMap(encodedNameKey).get(target)?.get(resolvedMimeType); + return getEncodedNamesMap(program, target)?.get(resolvedMimeType); } /** @@ -104,7 +104,7 @@ export function validateEncodedNamesConflicts(program: Program) { return tracker; } - for (const [target, map] of program.stateMap(encodedNameKey).entries()) { + for (const [target, map] of getEncodedNamesStateMap(program).entries()) { const scope = getScope(target); if (scope === undefined) { return; diff --git a/packages/compiler/src/lib/service.ts b/packages/compiler/src/lib/service.ts index ecfb74e85be..201b5bc1d93 100644 --- a/packages/compiler/src/lib/service.ts +++ b/packages/compiler/src/lib/service.ts @@ -4,6 +4,7 @@ import { Type, getTypeName, reportDeprecated } from "../core/index.js"; import { reportDiagnostic } from "../core/messages.js"; import type { Program } from "../core/program.js"; import { DecoratorContext, Namespace } from "../core/types.js"; +import { useStateMap } from "./utils.js"; export interface ServiceDetails { title?: string; @@ -15,10 +16,9 @@ export interface Service extends ServiceDetails { type: Namespace; } -const serviceDetailsKey = Symbol.for("@typespec/compiler.services"); -function getServiceMap(program: Program): Map { - return program.stateMap(serviceDetailsKey) as Map; -} +const [getService, setService, getServiceMap] = useStateMap( + "@typespec/compiler.services", +); /** * List all the services defined in the TypeSpec program @@ -29,15 +29,15 @@ export function listServices(program: Program): Service[] { return [...getServiceMap(program).values()]; } -/** - * Get the service information for the given namespace. - * @param program Program - * @param namespace Service namespace - * @returns Service information or undefined if namespace is not a service namespace. - */ -export function getService(program: Program, namespace: Namespace): Service | undefined { - return getServiceMap(program).get(namespace); -} +export { + /** + * Get the service information for the given namespace. + * @param program Program + * @param namespace Service namespace + * @returns Service information or undefined if namespace is not a service namespace. + */ + getService, +}; /** * Check if the namespace is defined as a service. @@ -62,7 +62,7 @@ export function addService( ): void { const serviceMap = getServiceMap(program); const existing = serviceMap.get(namespace) ?? {}; - serviceMap.set(namespace, { ...existing, ...details, type: namespace }); + setService(program, namespace, { ...existing, ...details, type: namespace }); } export const $service: ServiceDecorator = ( diff --git a/packages/compiler/src/lib/utils.ts b/packages/compiler/src/lib/utils.ts new file mode 100644 index 00000000000..61b1c57983a --- /dev/null +++ b/packages/compiler/src/lib/utils.ts @@ -0,0 +1,14 @@ +import type { Type } from "../core/types.js"; +import { unsafe_useStateMap, unsafe_useStateSet } from "../experimental/state-accessor.js"; + +function createStateSymbol(name: string) { + return Symbol.for(`TypeSpec.${name}`); +} + +export function useStateMap(key: string) { + return unsafe_useStateMap(createStateSymbol(key)); +} + +export function useStateSet(key: string) { + return unsafe_useStateSet(createStateSymbol(key)); +} diff --git a/packages/http-server-csharp/src/utils.ts b/packages/http-server-csharp/src/utils.ts index 06fbd3e74e8..872c5b52e91 100644 --- a/packages/http-server-csharp/src/utils.ts +++ b/packages/http-server-csharp/src/utils.ts @@ -146,7 +146,7 @@ export function getCSharpType( } let name: string = type.name; if (isTemplateInstance(type)) { - name = getFriendlyName(program, type); + name = getFriendlyName(program, type)!; } return { type: new CSharpType({ @@ -538,7 +538,7 @@ export function getModelAttributes( export function getModelInstantiationName(program: Program, model: Model, name: string): string { const friendlyName = getFriendlyName(program, model); - if (friendlyName?.length > 0) return friendlyName; + if (friendlyName && friendlyName.length > 0) return friendlyName; if (name === undefined || name.length < 1) name = ensureCSharpIdentifier(program, model, "", NameCasingType.Class); const names: string[] = [name]; diff --git a/tsconfig.ws.json b/tsconfig.ws.json index f20e06a030f..4ff471246a2 100644 --- a/tsconfig.ws.json +++ b/tsconfig.ws.json @@ -20,7 +20,8 @@ { "path": "packages/json-schema/tsconfig.json" }, { "path": "packages/best-practices/tsconfig.json" }, { "path": "packages/xml/tsconfig.json" }, - { "path": "packages/http-server-javascript/tsconfig.json" } + { "path": "packages/http-server-javascript/tsconfig.json" }, + { "path": "packages/http-server-csharp/tsconfig.json" } ], "files": [] }