diff --git a/docs/effect-schema-facade-emit-samples.md b/docs/effect-schema-facade-emit-samples.md new file mode 100644 index 00000000000..2fc36e04c9c --- /dev/null +++ b/docs/effect-schema-facade-emit-samples.md @@ -0,0 +1,196 @@ +# Facade emit — before/after samples + +Real `.d.ts` output for each schema-model kind, **stock tsc 6.0.3** vs the **patched** compiler +(effect-app ≥ 4.0.0-beta.280). Generated from the fixture below; long `withConstructorDefault` / +`mapFields` / `copy` machinery is elided as `…` for readability — the point is the **base type** +and the **generated `namespace` / `interface`**. + +## Source (fixture) + +```ts +import * as S from "effect-app/Schema" + +// 1. Opaque — no Encoded namespace +export class OpaqueNoEncoded extends S.Opaque()(S.Struct({ + name: S.String, + age: S.Number +})) {} + +// 2. Opaque +export class OpaqueWithEncoded extends S.Opaque()(S.Struct({ + id: S.String, + count: S.Number +})) {} +export namespace OpaqueWithEncoded { + export interface Encoded extends S.StructNestedEncoded {} +} + +// 3. Class +export class ClassNoEncoded extends S.Class("ClassNoEncoded")({ title: S.String }) {} + +// 4. Class +export class ClassWithEncoded extends S.Class("ClassWithEncoded")({ label: S.String }) {} +export namespace ClassWithEncoded { + export interface Encoded extends S.StructNestedEncoded {} +} + +// 5. Struct +export const MyStruct = S.Struct({ a: S.String, b: S.Number }) +export type MyStruct = typeof MyStruct.Type +``` + +--- + +## 1. `Opaque` (no Encoded) + +**Stock** — the base carries the full inline `S.Struct<{…}>`; no namespace at all: + +```ts +declare const OpaqueNoEncoded_base: S.Opaque, {}> & Omit, keyof S.Top>; +export declare class OpaqueNoEncoded extends OpaqueNoEncoded_base {} +``` + +**Patched** — the `S.Struct<{…}>` is gone; base is the compact facade, and `Encoded`/`Make`/services +are synthesized into a namespace (none existed in source): + +```ts +declare const OpaqueNoEncoded_base: S.OpaqueFacade & { + readonly fields: { readonly name: S.String; readonly age: … }; // statics preserved, elided + mapFields: …; readonly copy: …; +}; +export declare class OpaqueNoEncoded extends OpaqueNoEncoded_base {} +export interface OpaqueNoEncoded { readonly name: string; readonly age: number; } +export declare namespace OpaqueNoEncoded { + interface Encoded { readonly name: string; readonly age: number; } + interface Make { readonly name: string; readonly age: number; } + type DecodingServices = never; + type EncodingServices = never; +} +``` + +## 2. `Opaque` + +**Stock** — same inline `S.Struct<{…}>` in the base; only the source-written `Encoded extends StructNestedEncoded` namespace: + +```ts +declare const OpaqueWithEncoded_base: S.Opaque, {}> & Omit, keyof S.Top>; +export declare class OpaqueWithEncoded extends OpaqueWithEncoded_base {} +export declare namespace OpaqueWithEncoded { + interface Encoded extends S.StructNestedEncoded {} // conditional, recomputed per consumer +} +``` + +**Patched** — identical shape to case 1: the supplied `Encoded` is materialized to a literal, and `Make`/services are added: + +```ts +declare const OpaqueWithEncoded_base: S.OpaqueFacade & { … statics … }; +export declare class OpaqueWithEncoded extends OpaqueWithEncoded_base {} +export interface OpaqueWithEncoded { readonly id: string; readonly count: number; } +export declare namespace OpaqueWithEncoded { + interface Encoded { readonly id: string; readonly count: number; } // literal, not conditional + interface Make { readonly id: string; readonly count: number; } + type DecodingServices = never; + type EncodingServices = never; +} +``` + +> `Opaque` and `Opaque` produce the **same** patched output — the facade synthesizes +> the full namespace either way. + +## 3. `Class` + +**Stock** — `EnhancedClass` carries the full inline `Struct<{…}>`; no namespace: + +```ts +declare const ClassNoEncoded_base: S.EnhancedClass, {}>; +export declare class ClassNoEncoded extends ClassNoEncoded_base {} +``` + +**Patched** — `S.OpaqueClassFacade` (+ `identifier`/`fields`/… from the effect-app facade & statics), full namespace: + +```ts +declare const ClassNoEncoded_base: S.OpaqueClassFacade & { readonly fields: { title: S.String }; mapFields: …; readonly copy: …; }; +export declare class ClassNoEncoded extends ClassNoEncoded_base {} +export interface ClassNoEncoded { readonly title: string; } +export declare namespace ClassNoEncoded { + interface Encoded { readonly title: string; } + interface Make { readonly title: string; } + type DecodingServices = never; + type EncodingServices = never; +} +``` + +## 4. `Class` + +**Stock** — `EnhancedClass` with the inline `Struct<{…}>` wrapped to override `Encoded`: + +```ts +declare const ClassWithEncoded_base: S.EnhancedClass, "Encoded"> & { readonly Encoded: ClassWithEncoded.Encoded; }, {}>; +export declare class ClassWithEncoded extends ClassWithEncoded_base {} +export declare namespace ClassWithEncoded { + interface Encoded extends S.StructNestedEncoded {} +} +``` + +**Patched** — same as case 3 (`OpaqueClassFacade` + materialized namespace): + +```ts +declare const ClassWithEncoded_base: S.OpaqueClassFacade & { … statics … }; +export declare class ClassWithEncoded extends ClassWithEncoded_base {} +export interface ClassWithEncoded { readonly label: string; } +export declare namespace ClassWithEncoded { + interface Encoded { readonly label: string; } + interface Make { readonly label: string; } + type DecodingServices = never; + type EncodingServices = never; +} +``` + +> Error models (`S.ErrorClass`/`S.TaggedErrorClass`) are identical but emit `S.OpaqueErrorFacadeClass` +> with the `Cause.YieldableError` brand preserved. + +## 5. `Struct` + +**Stock** — the giant inline `S.Struct<{…}>` is the const's type; only a `type X = typeof X.Type` companion: + +```ts +export declare const MyStruct: S.Struct<{ + readonly a: S.String; + readonly b: import("effect/Schema").Number & { withConstructorDefault: …; withDecodingDefaultType: … }; +}>; +export type MyStruct = typeof MyStruct.Type; +``` + +**Patched** — `S.StructFacade` (a real effect-app `Struct`, Workflow-compatible), with `Fields`/`Encoded`/`Make`/services materialized; the `type X` companion is dropped for an `interface X`: + +```ts +export declare const MyStruct: S.StructFacade; +export interface MyStruct { readonly a: string; readonly b: number; } +export declare namespace MyStruct { + interface Fields { readonly a: S.String; readonly b: … } + interface Encoded { readonly a: string; readonly b: number; } + interface Make { readonly a: string; readonly b: number; } + type DecodingServices = never; + type EncodingServices = never; +} +``` + +--- + +The win: stock inlines the full `S.Struct<{…field machinery…}>` in every model's emitted base (and +re-derives `Encoded`/`Type` at each consumer); patched replaces it with a compact facade + named +namespace interfaces materialized **once**, so consumers read `X.Encoded` / `X.Type` by name. +See `effect-schema-facade-emit.md` for the why/how and measured −29.5% instantiation drop. diff --git a/docs/effect-schema-facade-emit.md b/docs/effect-schema-facade-emit.md new file mode 100644 index 00000000000..7a817395a3c --- /dev/null +++ b/docs/effect-schema-facade-emit.md @@ -0,0 +1,131 @@ +# Effect schema facade emit (`.d.ts` declaration step) — Go + +Fork of `microsoft/typescript-go` that expands effect-app schema models into compact, named +**facades** when it writes declaration files — the Go mirror of the same change in +`effect-app/TypeScript`. Source files stay clean; each model is expanded **once** in the emitted +`.d.ts`, so downstream consumers read cheap named interfaces instead of re-instantiating schema +generics. Also re-expressed as `_patches/029-031` on **effect-app/tsgo** (Effect LSP hooks + this). + +--- + +## What + +For every effect-app schema model the declaration emitter rewrites the emitted type and generates +a companion namespace: + +- **Class / error models** (`S.Class`, `S.TaggedClass`, `S.ErrorClass`, `S.TaggedErrorClass`): + the hoisted `X_base` const's type is rewritten from `S.EnhancedClass, Inherited>` + to the matching facade, plus a generated `namespace X` + `Type` `interface X`. +- **Opaque models / requests** (`S.Opaque`, `Req.Query`/`Req.Command`): via `OpaqueFacade`. +- **Struct models** (`S.Struct`, `S.TaggedStruct` — top-level `const`): the inline `S.Struct<{…}>` + is replaced by `S.StructFacade<…>`, with a sibling `interface X` (decoded `Self`) and a + **type-only** `declare namespace X` (`Fields`/`Encoded`/`Make`/services) that merges with the + `const`. The source `export type X = typeof X.Type` companion is dropped. + +Constructor → facade mapping: + +| source constructor | emitted base | brand (6th arg) | +|---|---|---| +| `S.Class` / `S.TaggedClass` | `S.OpaqueClassFacade` | `{}` | +| `S.ErrorClass` / `S.TaggedErrorClass` | `S.OpaqueErrorFacadeClass` | `Cause.YieldableError` | +| `S.Opaque` / `S.OpaqueFacade` / requests | `S.OpaqueFacade` | `{}` | +| `S.Struct` / `S.TaggedStruct` | `S.StructFacade` | — (6th arg is `X.Fields`) | + +Facade types live in **effect-app** (`>= 4.0.0-beta.279`). Exact interfaces (effect-app/libs `@`f74ba9e): + +- [`OpaqueFacade`](https://github.com/effect-app/libs/blob/f74ba9e6b6805010ec2ff54dd7db119209ea5521/packages/effect-app/src/Schema/Class.ts#L471) +- [`OpaqueClassFacade`](https://github.com/effect-app/libs/blob/f74ba9e6b6805010ec2ff54dd7db119209ea5521/packages/effect-app/src/Schema/Class.ts#L502) +- [`OpaqueErrorFacadeClass`](https://github.com/effect-app/libs/blob/f74ba9e6b6805010ec2ff54dd7db119209ea5521/packages/effect-app/src/Schema/Class.ts#L580) +- [`StructFacade`](https://github.com/effect-app/libs/blob/f74ba9e6b6805010ec2ff54dd7db119209ea5521/packages/effect-app/src/Schema/Class.ts#L654) +- [`EnhancedClass`](https://github.com/effect-app/libs/blob/f74ba9e6b6805010ec2ff54dd7db119209ea5521/packages/effect-app/src/Schema/Class.ts#L17) (stock class base we rewrite away) +- [effect-app `Struct`](https://github.com/effect-app/libs/blob/f74ba9e6b6805010ec2ff54dd7db119209ea5521/packages/effect-app/src/Schema.ts#L204) (base `StructFacade` extends — effect-app's own, not effect core's) + +Activation guard: the transform only runs for source files whose nearest `package.json` references +`effect-app` or an `@effect-app/*` package. Projects that only use `effect` keep stock declaration +emit and never reference effect-app-only `S.*Facade` names. It can also be disabled explicitly with +`--disableEffectAppDtsFacades` or `"disableEffectAppDtsFacades": true`. + +--- + +## Why + +Effect schemas encode their decoded/encoded/make/services views as type-level fields computed from +`fields`. Without expansion every consumer re-instantiates those generics (and risks depth-limit +blowups inferring `any`/`unknown`). The win is two parts: **named view interfaces** (~24%) and the +**class-base facade** that drops `S.Struct<{…full…}>` (~76%). Doing both at `.d.ts` emit makes +codegen purely text-based, keeps source clean, and expands each model once. + +The facade pins the whole `S.Bottom` surface, so consumers never re-derive any Bottom field from +`fields`. + +**Why it compounds:** TypeScript's instantiation cache is **per `Program`**, not global. Each +project reference is its own `Program` and re-instantiates every schema generic it touches from the +dependency `.d.ts`; each parallel checker has its own cache (tsgo multi ≈ 2.2× single); editor / +`tsc` / build / test configs are each another program. So a stock model (inline `S.Struct<{…full…}>` ++ conditional `Encoded`/`Type`) is re-derived **once per program** — N programs = N re-derivations. +The facade materializes the named interfaces **once** at emit, so every program reads cheap literals. +The saving therefore **scales with the number of programs / project references / parallel checkers**; +a many-reference monorepo is the worst case for stock and the best case for the facade. + +**Not just performance — correctness.** When the schema generics get deep enough the checker hits its +instantiation/depth limits and **silently** infers `unknown` / `any` for whole views (services, +constructor / `make` members, `Type` / `Encoded`) — no error, the program type-checks green against a +degraded type. It is **non-deterministic** and shows up most under **tsgo's default multi-threaded +mode** (each worker hits the wall independently). Materializing the views as named literals once at +emit makes them fully resolved and stable for every consumer; adopting it surfaced **several real +bugs** in our codebase that the silent `any`/`unknown` had masked. So this removes a class of silent, +non-deterministic type degradations on top of the instantiation cut. + +--- + +## How + +Three files (kept minimal so the diff re-applies as `_patches/` on Effect-TS/tsgo): + +- `internal/printer/emitresolver.go` — add `CreateTypeOfStructSchemaProperty` to the `EmitResolver` + interface. +- `internal/checker/emitresolver.go` — implement it (reads a property off the struct const's + initializer type and serializes the resolved type via `NodeBuilder.TypeToTypeNode(… | + FlagsUseFullyQualifiedType | FlagsMultilineObjectLiterals)`), factored to share + `getTypeOfSchemaExpressionProperty` with the class path. +- `internal/transformers/declarations/transform.go` — the transform + (`createEffectSchemaSourceFileDeclarations` + helpers). + +Key mechanics (identical semantics to the TS fork): + +- **Materialize from source, serialize the resolved type** — keeps `never` services as `never`, + avoids checker flow/position crashes from synthesized references. +- **`identifier` is not compiler-emitted** — it's on the class facade interfaces in effect-app + (`OpaqueClassFacade`/`OpaqueErrorFacadeClass`), not `OpaqueFacade`. The compiler emits only + per-model precise statics (`fields`/`mapFields`/`to`/`from`/`copy`). +- **Struct stays a `const`** — a type-only `declare namespace X` merges with it; no fake class. +- **`StructFacade` is Workflow-compatible** — `Omit, …> & { pinned members }`, + still a real effect-app struct schema (`Workflow.AnyStructSchema`, `Struct`, + `Union`, `.fields.x` keep working). + +### Gotchas +- `FlagsUseFullyQualifiedType` is mandatory (nested model refs otherwise collapse to bare sibling names). +- Don't materialize via a type-literal builder (coerces `never`→`{}`); serialize the resolved type. +- `StructFacade` must extend effect-app's `Struct`, not `effect/Schema`'s (distinct types). + +--- + +## Results + +Parity with the TS fork on the scanner `api` tree: **0 errors**, byte-identical facade emit, and the +same 112 models faceted (**56 `StructFacade` + 46 `OpaqueErrorFacadeClass` + 10 `OpaqueClassFacade`**). +TS-fork numbers (same source, stock tsc 6.0.3 vs patched): type instantiations 15,704,767 → +11,077,561 (**−29.5%**), check time 21.50s → 17.71s (−17.6%). + +**effect-app/tsgo** (`_patches/029-031`): patches apply cleanly in order after Effect's `001-028` +(same pinned submodule `dc37b5249`, no file overlap); the patched binary runs the scanner with +**0 errors + Effect LSP diagnostics (hooks live) + our facades (emitter live)** — the two +enhancements compose. + +--- + +## Related + +- [effect-app/typescript-go#2](https://github.com/effect-app/typescript-go/pull/2) (this fork) — tsc original [effect-app/TypeScript#3](https://github.com/effect-app/TypeScript/pull/3); patch form [effect-app/tsgo#1](https://github.com/effect-app/tsgo/pull/1). +- [effect-app/libs#801](https://github.com/effect-app/libs/pull/801) (facade `identifier` + `StructFacade`) + the `StructFacade` effect-app-`Struct` fix on [`main`](https://github.com/effect-app/libs/commits/main). +- Full plan / measurements / fork-layering: scanner [macs-holding/scanner#1597](https://github.com/macs-holding/scanner/pull/1597) (`docs/planning/dts-emit-schema-codegen.md`). diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go index ca96d8a0911..e3040fe0ef5 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -12,6 +12,7 @@ import ( "github.com/microsoft/typescript-go/internal/jsnum" "github.com/microsoft/typescript-go/internal/nodebuilder" "github.com/microsoft/typescript-go/internal/printer" + "github.com/microsoft/typescript-go/internal/scanner" ) var _ printer.EmitResolver = (*EmitResolver)(nil) @@ -1047,6 +1048,244 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) } +func (r *EmitResolver) CreateTypeOfTypeNode(emitContext *printer.EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + typeNode = emitContext.ParseNode(typeNode) + if typeNode == nil { + return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) + return requestNodeBuilder.TypeToTypeNode(r.checker.getTypeFromTypeNode(typeNode), enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) +} + +func (r *EmitResolver) CreateTypeLiteralOfTypeNode(emitContext *printer.EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + typeNode = emitContext.ParseNode(typeNode) + if typeNode == nil { + return nil + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + return r.createTypeLiteralOfType(emitContext, r.checker.getTypeFromTypeNode(typeNode), enclosingDeclaration, flags, internalFlags, tracker) +} + +func (r *EmitResolver) CreateTypeLiteralOfClassDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + declaration = emitContext.ParseNode(declaration) + if declaration == nil { + return nil + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + if schemaType := r.getTypeOfClassSchemaProperty(declaration, "Type"); schemaType != nil { + return r.createTypeLiteralOfType(emitContext, schemaType, enclosingDeclaration, flags, internalFlags, tracker) + } + symbol := r.checker.getSymbolOfDeclaration(declaration) + if symbol == nil { + return nil + } + return r.createTypeLiteralOfType(emitContext, r.checker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, flags, internalFlags, tracker) +} + +func (r *EmitResolver) CreateTypeLiteralOfClassStaticProperty(emitContext *printer.EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + declaration = emitContext.ParseNode(declaration) + if declaration == nil { + return nil + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + propertyType := r.getTypeOfClassSchemaProperty(declaration, propertyName) + if propertyType == nil { + return nil + } + return r.createTypeLiteralOfType(emitContext, propertyType, enclosingDeclaration, flags, internalFlags, tracker) +} + +func (r *EmitResolver) CreateMakeTypeOfClassDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + declaration = emitContext.ParseNode(declaration) + if declaration == nil { + return nil + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + makeType := r.getTypeOfClassSchemaProperty(declaration, "~type.make.in") + typeType := r.getTypeOfClassSchemaProperty(declaration, "Type") + if makeType == nil || typeType == nil { + return nil + } + return r.createMakeTypeOfTypes(emitContext, makeType, typeType, enclosingDeclaration, flags, internalFlags, tracker) +} + +func (r *EmitResolver) CreateTypeOfClassStaticProperty(emitContext *printer.EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + declaration = emitContext.ParseNode(declaration) + if declaration == nil { + return nil + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + propertyType := r.getTypeOfClassSchemaProperty(declaration, propertyName) + if propertyType == nil { + propertyType = r.getTypeOfClassStaticProperty(declaration, propertyName) + } + if propertyType == nil { + return nil + } + requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) + return requestNodeBuilder.TypeToTypeNode(propertyType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) +} + +func (r *EmitResolver) getTypeOfClassSchemaProperty(declaration *ast.Node, propertyName string) *Type { + schemaExpression := getClassSchemaExpression(declaration) + if schemaExpression == nil { + return nil + } + return r.getTypeOfSchemaExpressionProperty(schemaExpression, propertyName) +} + +func (r *EmitResolver) getTypeOfSchemaExpressionProperty(schemaExpression *ast.Node, propertyName string) *Type { + schemaType := r.checker.getTypeOfExpression(schemaExpression) + property := r.checker.getPropertyOfType(schemaType, propertyName) + if property == nil { + return nil + } + return r.checker.GetTypeOfSymbolAtLocation(property, schemaExpression) +} + +// Like CreateTypeOfClassStaticProperty, but for a `const X = S.Struct(...)` schema value: +// reads propertyName (Encoded / Type / ~type.make.in / DecodingServices / ...) off the type +// of the const's initializer and serializes the resolved type. Serializing the resolved type +// keeps `never` as `never` and never synthesizes references that could fail to resolve. +func (r *EmitResolver) CreateTypeOfStructSchemaProperty(emitContext *printer.EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + declaration = emitContext.ParseNode(declaration) + if declaration == nil || !ast.IsVariableDeclaration(declaration) || declaration.AsVariableDeclaration().Initializer == nil { + return nil + } + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + propertyType := r.getTypeOfSchemaExpressionProperty(declaration.AsVariableDeclaration().Initializer, propertyName) + if propertyType == nil { + return nil + } + structNodeBuilder := NewNodeBuilder(r.checker, emitContext) + return structNodeBuilder.TypeToTypeNode(propertyType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) +} + +func getClassSchemaExpression(declaration *ast.Node) *ast.Node { + if declaration == nil || !ast.IsClassDeclaration(declaration) || declaration.AsClassDeclaration().HeritageClauses == nil || len(declaration.AsClassDeclaration().HeritageClauses.Nodes) == 0 { + return nil + } + heritageClause := declaration.AsClassDeclaration().HeritageClauses.Nodes[0] + if heritageClause == nil || len(heritageClause.AsHeritageClause().Types.Nodes) == 0 { + return nil + } + expression := heritageClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments().Expression + if expression == nil || !ast.IsCallExpression(expression) || expression.AsCallExpression().Arguments == nil || len(expression.AsCallExpression().Arguments.Nodes) == 0 { + return nil + } + return expression.AsCallExpression().Arguments.Nodes[0] +} + +func (r *EmitResolver) getTypeOfClassStaticProperty(declaration *ast.Node, propertyName string) *Type { + symbol := r.checker.getSymbolOfDeclaration(declaration) + if symbol == nil { + return nil + } + staticType := r.checker.getTypeOfSymbol(symbol) + property := r.checker.getPropertyOfType(staticType, propertyName) + if property == nil { + return nil + } + return r.checker.GetTypeOfSymbolAtLocation(property, declaration) +} + +func (r *EmitResolver) createTypeLiteralOfType(emitContext *printer.EmitContext, typ *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) + members := core.Map(r.checker.getPropertiesOfType(typ), func(property *ast.Symbol) *ast.Node { + propertyTypeNode := requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(property), enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) + if propertyTypeNode == nil { + propertyTypeNode = emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) + } + var optionalToken *ast.Node + if property.Flags&ast.SymbolFlagsOptional != 0 { + optionalToken = emitContext.Factory.NewToken(ast.KindQuestionToken) + } + return emitContext.Factory.NewPropertySignatureDeclaration( + emitContext.Factory.NewModifierList([]*ast.Node{emitContext.Factory.NewModifier(ast.KindReadonlyKeyword)}), + createPropertyName(emitContext, property.Name), + optionalToken, + propertyTypeNode, + nil, + ) + }) + return emitContext.Factory.NewTypeLiteralNode(emitContext.Factory.NewNodeList(members)) +} + +func (r *EmitResolver) createMakeTypeOfTypes(emitContext *printer.EmitContext, makeType *Type, typeType *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + isVoidish := func(typ *Type) bool { return typ.flags&(TypeFlagsVoid|TypeFlagsUndefined) != 0 } + var makeTypes []*Type + if makeType.flags&TypeFlagsUnion != 0 { + makeTypes = makeType.AsUnionType().types + } + hasVoid := core.Some(makeTypes, isVoidish) + objectMakeType := makeType + if makeTypes != nil { + for _, typ := range makeTypes { + if len(r.checker.getPropertiesOfType(typ)) > 0 { + objectMakeType = typ + break + } + } + } + makeProperties := r.checker.getPropertiesOfType(objectMakeType) + if len(makeProperties) == 0 { + requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) + return requestNodeBuilder.TypeToTypeNode(makeType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) + } + typeProperties := make(map[string]*ast.Symbol) + for _, property := range r.checker.getPropertiesOfType(typeType) { + typeProperties[property.Name] = property + } + requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) + members := core.Map(makeProperties, func(property *ast.Symbol) *ast.Node { + source := property + if typeProperty := typeProperties[property.Name]; typeProperty != nil { + source = typeProperty + } + propertyTypeNode := requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(source), enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) + if propertyTypeNode == nil { + propertyTypeNode = emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) + } + var optionalToken *ast.Node + if property.Flags&ast.SymbolFlagsOptional != 0 { + optionalToken = emitContext.Factory.NewToken(ast.KindQuestionToken) + } + return emitContext.Factory.NewPropertySignatureDeclaration( + emitContext.Factory.NewModifierList([]*ast.Node{emitContext.Factory.NewModifier(ast.KindReadonlyKeyword)}), + createPropertyName(emitContext, property.Name), + optionalToken, + propertyTypeNode, + nil, + ) + }) + literal := emitContext.Factory.NewTypeLiteralNode(emitContext.Factory.NewNodeList(members)) + if hasVoid { + return emitContext.Factory.NewUnionTypeNode(emitContext.Factory.NewNodeList([]*ast.Node{literal, emitContext.Factory.NewKeywordTypeNode(ast.KindVoidKeyword)})) + } + return literal +} + +func createPropertyName(emitContext *printer.EmitContext, name string) *ast.Node { + if scanner.IsIdentifierText(name, core.LanguageVariantStandard) { + return emitContext.Factory.NewIdentifier(name) + } + return emitContext.Factory.NewStringLiteral(name, ast.TokenFlagsNone) +} + func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node { container = emitContext.ParseNode(container) r.checkerMu.Lock() diff --git a/internal/core/compileroptions.go b/internal/core/compileroptions.go index b079097c540..2e774120ae4 100644 --- a/internal/core/compileroptions.go +++ b/internal/core/compileroptions.go @@ -37,6 +37,7 @@ type CompilerOptions struct { DeclarationMap Tristate `json:"declarationMap,omitzero"` DeduplicatePackages Tristate `json:"deduplicatePackages,omitzero"` DisableSizeLimit Tristate `json:"disableSizeLimit,omitzero"` + DisableEffectAppDtsFacades Tristate `json:"disableEffectAppDtsFacades,omitzero"` DisableSourceOfProjectReferenceRedirect Tristate `json:"disableSourceOfProjectReferenceRedirect,omitzero"` DisableSolutionSearching Tristate `json:"disableSolutionSearching,omitzero"` DisableReferencedProjectLoad Tristate `json:"disableReferencedProjectLoad,omitzero"` diff --git a/internal/diagnostics/diagnostics_generated.go b/internal/diagnostics/diagnostics_generated.go index 86ffdcea417..d85678d9053 100644 --- a/internal/diagnostics/diagnostics_generated.go +++ b/internal/diagnostics/diagnostics_generated.go @@ -4292,6 +4292,8 @@ var X_1_implementation = &Message{code: 100008, category: CategoryMessage, key: var Set_the_number_of_projects_to_build_concurrently = &Message{code: 100009, category: CategoryMessage, key: "Set_the_number_of_projects_to_build_concurrently_100009", text: "Set the number of projects to build concurrently."} +var Disable_effect_app_declaration_facade_emit = &Message{code: 100010, category: CategoryMessage, key: "Disable_effect_app_declaration_facade_emit_100010", text: "Disable effect-app declaration facade emit."} + var Deduplicate_packages_with_the_same_name_and_version = &Message{code: 100011, category: CategoryMessage, key: "Deduplicate_packages_with_the_same_name_and_version_100011", text: "Deduplicate packages with the same name and version."} var Loading = &Message{code: 100012, category: CategoryMessage, key: "Loading_100012", text: "Loading"} @@ -8602,6 +8604,8 @@ func keyToMessage(key Key) *Message { return X_1_implementation case "Set_the_number_of_projects_to_build_concurrently_100009": return Set_the_number_of_projects_to_build_concurrently + case "Disable_effect_app_declaration_facade_emit_100010": + return Disable_effect_app_declaration_facade_emit case "Deduplicate_packages_with_the_same_name_and_version_100011": return Deduplicate_packages_with_the_same_name_and_version case "Loading_100012": diff --git a/internal/diagnostics/extraDiagnosticMessages.json b/internal/diagnostics/extraDiagnosticMessages.json index 10ea4408af6..d5fdb665eb3 100644 --- a/internal/diagnostics/extraDiagnosticMessages.json +++ b/internal/diagnostics/extraDiagnosticMessages.json @@ -39,6 +39,10 @@ "category": "Message", "code": 100009 }, + "Disable effect-app declaration facade emit.": { + "category": "Message", + "code": 100010 + }, "Non-relative paths are not allowed. Did you forget a leading './'?": { "category": "Error", "code": 5090 diff --git a/internal/printer/emitresolver.go b/internal/printer/emitresolver.go index 05bff82b90a..f97de56851d 100644 --- a/internal/printer/emitresolver.go +++ b/internal/printer/emitresolver.go @@ -124,6 +124,13 @@ type EmitResolver interface { CreateTypeParametersOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node CreateLiteralConstValue(emitContext *EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node CreateTypeOfExpression(emitContext *EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeLiteralOfTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeLiteralOfClassDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeLiteralOfClassStaticProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateMakeTypeOfClassDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfClassStaticProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfStructSchemaProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node CreateLateBoundIndexSignatures(emitContext *EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node TryJSTypeNodeToTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node } diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 5c8ca238578..157fe8c68ec 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -5,6 +5,7 @@ import ( "iter" "slices" "strings" + "sync" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/collections" @@ -14,6 +15,7 @@ import ( "github.com/microsoft/typescript-go/internal/jsnum" "github.com/microsoft/typescript-go/internal/modulespecifiers" "github.com/microsoft/typescript-go/internal/nodebuilder" + "github.com/microsoft/typescript-go/internal/packagejson" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/transformers" @@ -25,6 +27,8 @@ type ReferencedFilePair struct { ref *ast.FileReference } +var effectAppPackageJsonDependencyCache sync.Map + type OutputPaths interface { DeclarationFilePath() string JsFilePath() string @@ -351,6 +355,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast statements := tx.Visitor().VisitNodes(node.Statements) combinedStatements = tx.transformAndReplaceLatePaintedStatements(statements) combinedStatements = tx.appendCjsExports(combinedStatements) + combinedStatements = tx.createEffectSchemaSourceFileDeclarations(combinedStatements) combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { @@ -2277,6 +2282,1188 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } +type effectSchemaRequestBaseInfo struct { + modelName string + brand *ast.Node +} + +func (tx *DeclarationTransformer) createEffectSchemaSourceFileDeclarations(statements *ast.StatementList) *ast.StatementList { + if statements == nil || tx.state.currentSourceFile == nil { + return statements + } + if !tx.canEmitEffectAppDtsFacades() { + return statements + } + + modelNames := map[string]bool{} + existingNamespaces := map[string]bool{} + classes := map[string]*ast.Node{} + schemaClasses := tx.getEffectSchemaOriginalClasses() + + for _, statement := range statements.Nodes { + if className := getEffectSchemaClassName(statement); className != "" && ast.IsClassDeclaration(statement) { + classes[className] = statement + } + } + + for _, statement := range statements.Nodes { + if isEffectSchemaModelNamespace(statement) || isEffectSchemaMaterializedModelNamespace(statement, classes) { + name := moduleDeclarationIdentifierName(statement) + if name != "" && schemaClasses[name] != nil { + modelNames[name] = true + existingNamespaces[name] = true + } + } + } + + requestBaseInfos := map[string]effectSchemaRequestBaseInfo{} + for _, statement := range statements.Nodes { + if info, ok := getEffectSchemaRequestBaseInfo(statement); ok && classes[info.modelName] != nil && schemaClasses[info.modelName] == nil { + requestBaseInfos[info.modelName] = info + } + } + + for className, classDeclaration := range schemaClasses { + if !existingNamespaces[className] && tx.canCreateEffectSchemaGeneratedNamespace(classDeclaration) { + modelNames[className] = true + } + } + + // Top-level `const X = S.Struct(...)` / `S.TaggedStruct(...)` schema values. Faceted on + // the const itself (it is a value, not a class): the giant `S.Struct<{...}>` annotation + // becomes a compact `StructFacade<...>` plus a generated `interface X` (decoded Self) and + // a type-only `declare namespace X`. + structModelNames := map[string]bool{} + for name := range tx.getEffectSchemaOriginalStructs() { + if modelNames[name] { + continue + } + if _, ok := requestBaseInfos[name]; ok { + continue + } + if hasTopLevelInterface(statements, name) || hasTopLevelNamespace(statements, name) { + continue + } + if tx.canCreateEffectSchemaGeneratedStructNamespace(name) { + structModelNames[name] = true + } + } + + if len(modelNames) == 0 && len(requestBaseInfos) == 0 && len(structModelNames) == 0 { + return statements + } + + changed := false + next := make([]*ast.Node, 0, len(statements.Nodes)) + for _, statement := range statements.Nodes { + baseModelName := getEffectSchemaBaseModelName(statement) + if baseModelName != "" && modelNames[baseModelName] { + classDeclaration := schemaClasses[baseModelName] + if classDeclaration == nil { + classDeclaration = classes[baseModelName] + } + if classDeclaration != nil { + if updated := tx.updateEffectSchemaBaseDeclaration(statement, baseModelName, classDeclaration, needsEffectSchemaIntermediateClass(classDeclaration)); updated != nil { + changed = true + next = append(next, updated) + continue + } + } + } + + if requestBaseInfo, ok := requestBaseInfos[baseModelName]; ok { + if updated := tx.updateEffectSchemaRequestBaseDeclaration(statement, requestBaseInfo); updated != nil { + changed = true + next = append(next, updated) + continue + } + } + + if isEffectSchemaModelNamespace(statement) || isEffectSchemaMaterializedModelNamespace(statement, classes) { + name := moduleDeclarationIdentifierName(statement) + classDeclaration := schemaClasses[name] + if classDeclaration == nil { + classDeclaration = classes[name] + } + if classDeclaration != nil { + if updated := tx.updateEffectSchemaNamespaceDeclaration(statement, classDeclaration); updated != nil { + changed = true + next = append(next, updated) + continue + } + } + } + + className := getEffectSchemaClassName(statement) + if requestBaseInfos[className].modelName != "" { + classDeclaration := statement + var typeInterface *ast.Node + if !hasTopLevelInterface(statements, className) { + typeInterface = tx.createEffectSchemaTypeInterface(classDeclaration) + } + var namespace *ast.Node + if !hasTopLevelNamespace(statements, className) { + namespace = tx.createEffectSchemaGeneratedNamespaceDeclaration(className, classDeclaration) + } + if typeInterface != nil || namespace != nil { + changed = true + next = append(next, statement) + if typeInterface != nil { + next = append(next, typeInterface) + } + if namespace != nil { + next = append(next, namespace) + } + continue + } + } + + if className != "" && modelNames[className] { + classDeclaration := schemaClasses[className] + if classDeclaration == nil { + classDeclaration = statement + } + usesIntermediate := needsEffectSchemaIntermediateClass(classDeclaration) + updatedClass := statement + if usesIntermediate { + updatedClass = tx.updateEffectSchemaClassDeclaration(statement, className) + } + var typeInterface *ast.Node + if !hasTopLevelInterface(statements, className) { + typeInterface = tx.createEffectSchemaTypeInterface(classDeclaration) + } + var namespace *ast.Node + if !existingNamespaces[className] { + namespace = tx.createEffectSchemaGeneratedNamespaceDeclaration(className, classDeclaration) + } + if updatedClass != statement || typeInterface != nil || namespace != nil { + changed = true + if usesIntermediate { + next = append(next, tx.createEffectSchemaIntermediateClass(className)) + } + next = append(next, updatedClass) + if typeInterface != nil { + next = append(next, typeInterface) + } + if namespace != nil { + existingNamespaces[className] = true + next = append(next, namespace) + } + continue + } + } + + if structName := getEffectSchemaStructVariableName(statement); structName != "" && structModelNames[structName] { + if declarations := tx.createEffectSchemaStructDeclarations(statement, structName); declarations != nil { + changed = true + next = append(next, declarations...) + continue + } + } + + if isEffectSchemaStructCompanionTypeAlias(statement, structModelNames) { + // Dropped — replaced by the generated `interface X`. + changed = true + continue + } + + next = append(next, statement) + } + + if !changed { + return statements + } + return tx.Factory().NewNodeList(next) +} + +func (tx *DeclarationTransformer) canEmitEffectAppDtsFacades() bool { + if tx.compilerOptions.DisableEffectAppDtsFacades.IsTrue() || tx.state.currentSourceFile == nil { + return false + } + dir := tspath.GetDirectoryPath(tx.state.currentSourceFile.FileName()) + packageDir := tx.host.GetNearestAncestorDirectoryWithPackageJson(dir) + if packageDir == "" { + return false + } + info := tx.host.GetPackageJsonInfo(tspath.CombinePaths(packageDir, "package.json")) + return packageJsonReferencesEffectApp(info.GetContents()) +} + +func packageJsonReferencesEffectApp(contents *packagejson.PackageJson) bool { + if contents == nil { + return false + } + if cached, ok := effectAppPackageJsonDependencyCache.Load(contents); ok { + return cached.(bool) + } + if contents.HasDependency("effect-app") { + effectAppPackageJsonDependencyCache.Store(contents, true) + return true + } + found := false + contents.RangeDependencies(func(name, version, dependencyField string) bool { + found = strings.HasPrefix(name, "@effect-app/") + return !found + }) + effectAppPackageJsonDependencyCache.Store(contents, found) + return found +} + +func (tx *DeclarationTransformer) getEffectSchemaOriginalClasses() map[string]*ast.Node { + classes := map[string]*ast.Node{} + for _, statement := range tx.state.currentSourceFile.Statements.Nodes { + if ast.IsClassDeclaration(statement) && statement.Name() != nil && hasEffectSchemaOpaqueHeritage(statement) { + classes[statement.Name().Text()] = statement + } + } + return classes +} + +// --- Struct/TaggedStruct const faceting --- + +func (tx *DeclarationTransformer) getEffectSchemaOriginalStructs() map[string]bool { + structs := map[string]bool{} + for _, statement := range tx.state.currentSourceFile.Statements.Nodes { + name := getEffectSchemaStructVariableName(statement) + if name == "" { + continue + } + decl := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] + if decl.AsVariableDeclaration().Initializer != nil && isEffectSchemaStructInitializer(decl.AsVariableDeclaration().Initializer) { + structs[name] = true + } + } + return structs +} + +func isEffectSchemaStructInitializer(expression *ast.Node) bool { + if !ast.IsCallExpression(expression) { + return false + } + callee := expression.AsCallExpression().Expression + if !ast.IsPropertyAccessExpression(callee) || callee.Name() == nil { + return false + } + name := callee.Name().Text() + if name != "Struct" && name != "TaggedStruct" { + return false + } + left := callee.Expression() + return left != nil && ast.IsIdentifier(left) && (left.Text() == "S" || left.Text() == "Schema") +} + +func getEffectSchemaStructVariableName(statement *ast.Node) string { + if !ast.IsVariableStatement(statement) || statement.AsVariableStatement().DeclarationList == nil { + return "" + } + declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes + if len(declarations) != 1 || declarations[0].Name() == nil || !ast.IsIdentifier(declarations[0].Name()) { + return "" + } + return declarations[0].Name().Text() +} + +func (tx *DeclarationTransformer) getEffectSchemaSourceStructDeclaration(modelName string) *ast.Node { + for _, statement := range tx.state.currentSourceFile.Statements.Nodes { + if getEffectSchemaStructVariableName(statement) != modelName { + continue + } + decl := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] + if decl.AsVariableDeclaration().Initializer != nil && isEffectSchemaStructInitializer(decl.AsVariableDeclaration().Initializer) { + return decl + } + } + return nil +} + +// Reads a property (Encoded / Type / ~type.make.in / fields / services) off the source +// struct value's type and serializes it; `never` services stay `never`. +func (tx *DeclarationTransformer) materializeEffectSchemaStructProperty(modelName string, propertyName string) *ast.Node { + declaration := tx.getEffectSchemaSourceStructDeclaration(modelName) + if declaration == nil { + return nil + } + return tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfStructSchemaProperty(tx.EmitContext(), declaration, propertyName, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) +} + +func (tx *DeclarationTransformer) canCreateEffectSchemaGeneratedStructNamespace(modelName string) bool { + return tx.materializeEffectSchemaStructProperty(modelName, "Encoded") != nil +} + +func (tx *DeclarationTransformer) createEffectSchemaStructInterfaceFromProperty(modelName string, propertyName string, declaredName string) *ast.Node { + typeNode := tx.materializeEffectSchemaStructProperty(modelName, propertyName) + if typeNode == nil { + return nil + } + if ast.IsTypeLiteralNode(typeNode) { + return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier(declaredName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) + } + return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier(declaredName), nil, typeNode) +} + +func (tx *DeclarationTransformer) createEffectSchemaStructServiceDeclaration(modelName string, name string) *ast.Node { + resolved := tx.materializeEffectSchemaStructProperty(modelName, name) + serviceType := resolved + if resolved == nil || resolved.Kind == ast.KindAnyKeyword { + serviceType = tx.Factory().NewKeywordTypeNode(ast.KindNeverKeyword) + } + return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier(name), nil, serviceType) +} + +func structHasExportModifier(statement *ast.Node) bool { + return ast.HasSyntacticModifier(statement, ast.ModifierFlagsExport) +} + +func (tx *DeclarationTransformer) effectSchemaStructModifiers(exported bool, includeDeclare bool) *ast.ModifierList { + modifiers := []*ast.Node{} + if exported { + modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindExportKeyword)) + } + if includeDeclare { + modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + if len(modifiers) == 0 { + return nil + } + return tx.Factory().NewModifierList(modifiers) +} + +func (tx *DeclarationTransformer) createEffectSchemaGeneratedStructNamespace(modelName string, exported bool) *ast.Node { + fields := tx.createEffectSchemaStructInterfaceFromProperty(modelName, "fields", "Fields") + if fields == nil { + return nil + } + encoded := tx.createEffectSchemaStructInterfaceFromProperty(modelName, "Encoded", "Encoded") + if encoded == nil { + return nil + } + statements := []*ast.Node{fields, encoded} + if makeDeclaration := tx.createEffectSchemaStructInterfaceFromProperty(modelName, "~type.make.in", "Make"); makeDeclaration != nil { + statements = append(statements, makeDeclaration) + } + statements = append(statements, tx.createEffectSchemaStructServiceDeclaration(modelName, "DecodingServices")) + statements = append(statements, tx.createEffectSchemaStructServiceDeclaration(modelName, "EncodingServices")) + return tx.Factory().NewModuleDeclaration( + tx.effectSchemaStructModifiers(exported, true), + ast.KindNamespaceKeyword, + tx.Factory().NewIdentifier(modelName), + tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(statements)), + ) +} + +// `S.StructFacade` — +// `StructFacade` is exported from effect-app (>= 4.0.0-beta.279), so it resolves through the +// file's own `S` (effect-app/Schema) import, exactly like `S.OpaqueFacade`. It extends +// `S.Struct`, so the faceted value stays Workflow-compatible. +func (tx *DeclarationTransformer) createEffectSchemaStructFacadeType(modelName string) *ast.Node { + member := func(name string) *ast.Node { + return tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(modelName), tx.Factory().NewIdentifier(name)), nil) + } + typeArguments := tx.Factory().NewNodeList([]*ast.Node{ + tx.Factory().NewTypeReferenceNode(tx.Factory().NewIdentifier(modelName), nil), + member("Encoded"), + member("Make"), + member("DecodingServices"), + member("EncodingServices"), + member("Fields"), + }) + return tx.Factory().NewTypeReferenceNode( + tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier("S"), tx.Factory().NewIdentifier("StructFacade")), + typeArguments, + ) +} + +func (tx *DeclarationTransformer) createEffectSchemaStructDeclarations(statement *ast.Node, modelName string) []*ast.Node { + typeNode := tx.materializeEffectSchemaStructProperty(modelName, "Type") + if typeNode == nil || !ast.IsTypeLiteralNode(typeNode) { + return nil + } + namespace := tx.createEffectSchemaGeneratedStructNamespace(modelName, structHasExportModifier(statement)) + if namespace == nil { + return nil + } + exported := structHasExportModifier(statement) + declaration := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] + updatedDeclaration := tx.Factory().UpdateVariableDeclaration( + declaration.AsVariableDeclaration(), + declaration.Name(), + declaration.AsVariableDeclaration().ExclamationToken, + tx.createEffectSchemaStructFacadeType(modelName), + declaration.AsVariableDeclaration().Initializer, + ) + declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) + declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) + retypedConst := tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) + typeInterface := tx.Factory().NewInterfaceDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) + return []*ast.Node{retypedConst, typeInterface, namespace} +} + +type generatedTypeImport struct { + importedName string + moduleSpecifier string +} + +func (tx *DeclarationTransformer) generatedTypeNamedImports() map[string]generatedTypeImport { + imports := map[string]generatedTypeImport{} + for _, statement := range tx.state.currentSourceFile.Statements.Nodes { + if !ast.IsImportDeclaration(statement) { + continue + } + decl := statement.AsImportDeclaration() + if decl.ModuleSpecifier == nil || !ast.IsStringLiteralLike(decl.ModuleSpecifier) || decl.ImportClause == nil || decl.ImportClause.AsImportClause().NamedBindings == nil { + continue + } + moduleSpecifier := decl.ModuleSpecifier.Text() + namedBindings := decl.ImportClause.AsImportClause().NamedBindings + if !ast.IsNamedImports(namedBindings) { + continue + } + for _, specifier := range namedBindings.AsNamedImports().Elements.Nodes { + importedName := specifier.Name().Text() + if specifier.AsImportSpecifier().PropertyName != nil { + importedName = specifier.AsImportSpecifier().PropertyName.Text() + } + imports[specifier.Name().Text()] = generatedTypeImport{importedName: importedName, moduleSpecifier: moduleSpecifier} + } + } + return imports +} + +func (tx *DeclarationTransformer) generatedTypeNamespaceImports() map[string]string { + imports := map[string]string{} + for _, statement := range tx.state.currentSourceFile.Statements.Nodes { + if !ast.IsImportDeclaration(statement) { + continue + } + decl := statement.AsImportDeclaration() + if decl.ModuleSpecifier == nil || !ast.IsStringLiteralLike(decl.ModuleSpecifier) || decl.ImportClause == nil || decl.ImportClause.AsImportClause().NamedBindings == nil { + continue + } + namedBindings := decl.ImportClause.AsImportClause().NamedBindings + if ast.IsNamespaceImport(namedBindings) { + imports[decl.ModuleSpecifier.Text()] = namedBindings.Name().Text() + } + } + return imports +} + +func (tx *DeclarationTransformer) normalizeGeneratedImportedTypes(typeNode *ast.Node) *ast.Node { + if typeNode == nil { + return nil + } + namedImports := tx.generatedTypeNamedImports() + if len(namedImports) == 0 { + return typeNode + } + namespaceImports := tx.generatedTypeNamespaceImports() + var visitor *ast.NodeVisitor + visitor = tx.EmitContext().NewNodeVisitor(func(node *ast.Node) *ast.Node { + if node != nil && node.Kind == ast.KindTypeReference { + typeReference := node.AsTypeReferenceNode() + if typeReference.TypeName != nil && typeReference.TypeName.Kind == ast.KindIdentifier { + imported := namedImports[typeReference.TypeName.Text()] + if imported.importedName == "" || imported.moduleSpecifier == "" { + return visitor.VisitEachChild(node) + } + typeArguments := visitor.VisitNodes(typeReference.TypeArguments) + if namespaceName := namespaceImports[imported.moduleSpecifier]; namespaceName != "" { + return tx.Factory().NewTypeReferenceNode( + tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(namespaceName), tx.Factory().NewIdentifier(imported.importedName)), + typeArguments, + ) + } + return tx.Factory().NewImportTypeNode( + false, + tx.Factory().NewLiteralTypeNode(tx.Factory().NewStringLiteral(imported.moduleSpecifier, ast.TokenFlagsNone)), + nil, + tx.Factory().NewIdentifier(imported.importedName), + typeArguments, + ) + } + } + return visitor.VisitEachChild(node) + }) + return visitor.VisitNode(typeNode) +} + +func isEffectSchemaStructCompanionTypeAlias(statement *ast.Node, structModelNames map[string]bool) bool { + return ast.IsTypeAliasDeclaration(statement) && statement.Name() != nil && structModelNames[statement.Name().Text()] +} + +func hasEffectSchemaOpaqueHeritage(classDeclaration *ast.Node) bool { + heritageType := getFirstHeritageType(classDeclaration) + if heritageType == nil { + return false + } + expression := heritageType.AsExpressionWithTypeArguments().Expression + if expression == nil { + return false + } + if ast.IsCallExpression(expression) { + expression = expression.AsCallExpression().Expression + } + if ast.IsCallExpression(expression) { + expression = expression.AsCallExpression().Expression + } + return isEffectSchemaOpaqueExpression(expression) +} + +func getFirstHeritageType(classDeclaration *ast.Node) *ast.Node { + if classDeclaration == nil || !ast.IsClassDeclaration(classDeclaration) || classDeclaration.AsClassDeclaration().HeritageClauses == nil { + return nil + } + clauses := classDeclaration.AsClassDeclaration().HeritageClauses.Nodes + if len(clauses) == 0 || clauses[0].AsHeritageClause().Types == nil || len(clauses[0].AsHeritageClause().Types.Nodes) == 0 { + return nil + } + return clauses[0].AsHeritageClause().Types.Nodes[0] +} + +func isEffectSchemaOpaqueExpression(expression *ast.Node) bool { + return getEffectSchemaCtorFacadeName(expression) != "" +} + +// Maps a schema-model heritage constructor (S.Opaque(...), S.Class(...), S.ErrorClass(...), ...) +// to the effect-app facade type its base should be rewritten to. Returns "" for non-model +// constructors. Opaque family (incl. requests) -> OpaqueFacade; class family -> OpaqueClassFacade; +// error family -> OpaqueErrorFacadeClass. +func getEffectSchemaCtorFacadeName(expression *ast.Node) string { + if !ast.IsPropertyAccessExpression(expression) || expression.Name() == nil || expression.Expression() == nil { + return "" + } + left := expression.Expression() + if !ast.IsIdentifier(left) || (left.Text() != "S" && left.Text() != "Schema") { + return "" + } + switch expression.Name().Text() { + case "Opaque", "OpaqueFacade": + return "OpaqueFacade" + case "Class", "TaggedClass": + return "OpaqueClassFacade" + case "ErrorClass", "TaggedErrorClass": + return "OpaqueErrorFacadeClass" + default: + return "" + } +} + +func getEffectSchemaClassFacadeName(classDeclaration *ast.Node) string { + heritageType := getFirstHeritageType(classDeclaration) + if heritageType == nil { + return "OpaqueFacade" + } + expression := heritageType.AsExpressionWithTypeArguments().Expression + for expression != nil && ast.IsCallExpression(expression) { + expression = expression.AsCallExpression().Expression + } + if expression != nil { + if name := getEffectSchemaCtorFacadeName(expression); name != "" { + return name + } + } + return "OpaqueFacade" +} + +func isEffectSchemaModelNamespace(statement *ast.Node) bool { + if !ast.IsModuleDeclaration(statement) || statement.Name() == nil || !ast.IsIdentifier(statement.Name()) || statement.AsModuleDeclaration().Body == nil || statement.AsModuleDeclaration().Body.Kind != ast.KindModuleBlock { + return false + } + return core.Some(statement.AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes, isEffectSchemaStructNestedEncodedInterface) +} + +func isEffectSchemaMaterializedModelNamespace(statement *ast.Node, classes map[string]*ast.Node) bool { + name := moduleDeclarationIdentifierName(statement) + if name == "" || classes[name] == nil || statement.AsModuleDeclaration().Body == nil || statement.AsModuleDeclaration().Body.Kind != ast.KindModuleBlock { + return false + } + return core.Some(statement.AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes, isEffectSchemaEncodedInterface) +} + +func moduleDeclarationIdentifierName(statement *ast.Node) string { + if !ast.IsModuleDeclaration(statement) || statement.Name() == nil || !ast.IsIdentifier(statement.Name()) { + return "" + } + return statement.Name().Text() +} + +func isEffectSchemaStructNestedEncodedInterface(statement *ast.Node) bool { + if !ast.IsInterfaceDeclaration(statement) || statement.Name() == nil || statement.Name().Text() != "Encoded" || statement.AsInterfaceDeclaration().HeritageClauses == nil { + return false + } + clauses := statement.AsInterfaceDeclaration().HeritageClauses.Nodes + if len(clauses) != 1 || clauses[0].AsHeritageClause().Types == nil || len(clauses[0].AsHeritageClause().Types.Nodes) != 1 { + return false + } + heritageType := clauses[0].AsHeritageClause().Types.Nodes[0] + expression := heritageType.AsExpressionWithTypeArguments().Expression + typeArguments := heritageType.AsExpressionWithTypeArguments().TypeArguments + return ast.IsPropertyAccessExpression(expression) && + expression.Name() != nil && + expression.Name().Text() == "StructNestedEncoded" && + typeArguments != nil && + len(typeArguments.Nodes) == 1 && + typeArguments.Nodes[0].Kind == ast.KindTypeQuery +} + +func isEffectSchemaStructNestedEncodedInterfaceForModel(statement *ast.Node, modelName string) bool { + if !isEffectSchemaStructNestedEncodedInterface(statement) { + return false + } + typeQuery := statement.AsInterfaceDeclaration().HeritageClauses.Nodes[0].AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments().TypeArguments.Nodes[0] + exprName := typeQuery.AsTypeQueryNode().ExprName + return exprName != nil && ast.IsIdentifier(exprName) && exprName.Text() == modelName +} + +func (tx *DeclarationTransformer) materializeSchemaNestedEncoded(encoded *ast.Node) *ast.Node { + heritageType := encoded.AsInterfaceDeclaration().HeritageClauses.Nodes[0].AsHeritageClause().Types.Nodes[0] + return tx.resolver.CreateTypeLiteralOfTypeNode(tx.EmitContext(), heritageType, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) +} + +func (tx *DeclarationTransformer) createEffectSchemaMaterializedEncodedDeclaration(encoded *ast.Node, classDeclaration *ast.Node) *ast.Node { + encodedType := tx.materializeSchemaNestedEncoded(encoded) + if encodedType == nil || !ast.IsTypeLiteralNode(encodedType) { + return nil + } + return tx.Factory().NewInterfaceDeclaration(encoded.Modifiers(), encoded.Name(), encoded.AsInterfaceDeclaration().TypeParameters, nil, tx.Factory().NewNodeList(encodedType.AsTypeLiteralNode().Members.Nodes)) +} + +func isEffectSchemaEncodedInterface(statement *ast.Node) bool { + return ast.IsInterfaceDeclaration(statement) && statement.Name() != nil && statement.Name().Text() == "Encoded" +} + +func (tx *DeclarationTransformer) canCreateEffectSchemaGeneratedNamespace(classDeclaration *ast.Node) bool { + return tx.createEffectSchemaEncodedDeclaration(classDeclaration) != nil +} + +func getEffectSchemaClassName(statement *ast.Node) string { + if ast.IsClassDeclaration(statement) && statement.Name() != nil { + return statement.Name().Text() + } + return "" +} + +func hasTopLevelInterface(statements *ast.StatementList, name string) bool { + return core.Some(statements.Nodes, func(statement *ast.Node) bool { + return ast.IsInterfaceDeclaration(statement) && statement.Name() != nil && statement.Name().Text() == name + }) +} + +func hasTopLevelNamespace(statements *ast.StatementList, name string) bool { + return core.Some(statements.Nodes, func(statement *ast.Node) bool { + return moduleDeclarationIdentifierName(statement) == name + }) +} + +func (tx *DeclarationTransformer) createEffectSchemaTypeInterface(classDeclaration *ast.Node) *ast.Node { + if classDeclaration == nil || classDeclaration.Name() == nil { + return nil + } + literal := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeLiteralOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if literal == nil || !ast.IsTypeLiteralNode(literal) { + return nil + } + return tx.Factory().NewInterfaceDeclaration( + tx.createEffectSchemaNamespaceModifiers(classDeclaration, false), + tx.Factory().NewIdentifier(classDeclaration.Name().Text()), + nil, + nil, + tx.Factory().NewNodeList(literal.AsTypeLiteralNode().Members.Nodes), + ) +} + +func (tx *DeclarationTransformer) updateEffectSchemaNamespaceDeclaration(namespace *ast.Node, classDeclaration *ast.Node) *ast.Node { + body := namespace.AsModuleDeclaration().Body + if body == nil || body.Kind != ast.KindModuleBlock { + return nil + } + replacedEncoded := false + existing := map[string]bool{} + kept := make([]*ast.Node, 0, len(body.AsModuleBlock().Statements.Nodes)) + for _, statement := range body.AsModuleBlock().Statements.Nodes { + if (ast.IsInterfaceDeclaration(statement) || ast.IsTypeAliasDeclaration(statement)) && statement.Name() != nil { + name := statement.Name().Text() + if name == "Encoded" { + var encodedDeclaration *ast.Node + if isEffectSchemaStructNestedEncodedInterfaceForModel(statement, moduleDeclarationIdentifierName(namespace)) { + encodedDeclaration = tx.createEffectSchemaMaterializedEncodedDeclaration(statement, classDeclaration) + } + if encodedDeclaration != nil { + kept = append(kept, encodedDeclaration) + replacedEncoded = true + continue + } + } else { + existing[name] = true + if name == "Make" || name == "DecodingServices" || name == "EncodingServices" { + continue + } + } + } + kept = append(kept, statement) + } + + additions := []*ast.Node{} + if makeDeclaration := tx.createEffectSchemaMakeDeclaration(classDeclaration); makeDeclaration != nil { + additions = append(additions, makeDeclaration) + } + if decodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "DecodingServices"); decodingServices != nil { + additions = append(additions, decodingServices) + } + if encodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "EncodingServices"); encodingServices != nil { + additions = append(additions, encodingServices) + } + if !existing["Fields"] { + if fieldsDeclaration := tx.createEffectSchemaFieldsDeclaration(classDeclaration); fieldsDeclaration != nil { + additions = append([]*ast.Node{fieldsDeclaration}, additions...) + } + } + if !replacedEncoded && len(additions) == 0 && len(existing) == 0 { + return nil + } + + statements := tx.Factory().NewNodeList(append(kept, additions...)) + moduleBlock := tx.Factory().UpdateModuleBlock(body.AsModuleBlock(), statements) + return tx.Factory().UpdateModuleDeclaration(namespace.AsModuleDeclaration(), namespace.Modifiers(), namespace.AsModuleDeclaration().Keyword, namespace.Name(), moduleBlock) +} + +func (tx *DeclarationTransformer) createEffectSchemaGeneratedNamespaceDeclaration(modelName string, classDeclaration *ast.Node) *ast.Node { + encoded := tx.createEffectSchemaEncodedDeclaration(classDeclaration) + if encoded == nil { + return nil + } + statements := []*ast.Node{encoded} + if fieldsDeclaration := tx.createEffectSchemaFieldsDeclaration(classDeclaration); fieldsDeclaration != nil { + statements = append(statements, fieldsDeclaration) + } + if makeDeclaration := tx.createEffectSchemaMakeDeclaration(classDeclaration); makeDeclaration != nil { + statements = append(statements, makeDeclaration) + } + if decodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "DecodingServices"); decodingServices != nil { + statements = append(statements, decodingServices) + } + if encodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "EncodingServices"); encodingServices != nil { + statements = append(statements, encodingServices) + } + return tx.Factory().NewModuleDeclaration( + tx.createEffectSchemaNamespaceModifiers(classDeclaration, true), + ast.KindNamespaceKeyword, + tx.Factory().NewIdentifier(modelName), + tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(statements)), + ) +} + +func (tx *DeclarationTransformer) createEffectSchemaNamespaceModifiers(classDeclaration *ast.Node, includeDeclare bool) *ast.ModifierList { + modifiers := []*ast.Node{} + if ast.HasSyntacticModifier(classDeclaration, ast.ModifierFlagsExport) { + modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindExportKeyword)) + } + if includeDeclare { + modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + if len(modifiers) == 0 { + return nil + } + return tx.Factory().NewModifierList(modifiers) +} + +func (tx *DeclarationTransformer) createEffectSchemaFieldsDeclaration(classDeclaration *ast.Node) *ast.Node { + fieldsType := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "fields", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if fieldsType == nil || fieldsType.Kind == ast.KindAnyKeyword { + return nil + } + if ast.IsTypeLiteralNode(fieldsType) { + return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier("Fields"), nil, nil, tx.Factory().NewNodeList(fieldsType.AsTypeLiteralNode().Members.Nodes)) + } + return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier("Fields"), nil, fieldsType) +} + +func (tx *DeclarationTransformer) createEffectSchemaEncodedDeclaration(classDeclaration *ast.Node) *ast.Node { + encodedType := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "Encoded", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if encodedType == nil { + return nil + } + if ast.IsTypeLiteralNode(encodedType) { + return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier("Encoded"), nil, nil, tx.Factory().NewNodeList(encodedType.AsTypeLiteralNode().Members.Nodes)) + } + return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier("Encoded"), nil, encodedType) +} + +func (tx *DeclarationTransformer) createEffectSchemaMakeDeclaration(classDeclaration *ast.Node) *ast.Node { + makeType := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateMakeTypeOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if makeType == nil { + makeType = tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "~type.make.in", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + } + if makeType == nil { + return nil + } + if ast.IsTypeLiteralNode(makeType) { + return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier("Make"), nil, nil, tx.Factory().NewNodeList(makeType.AsTypeLiteralNode().Members.Nodes)) + } + return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier("Make"), nil, makeType) +} + +func (tx *DeclarationTransformer) createEffectSchemaServiceDeclaration(classDeclaration *ast.Node, name string) *ast.Node { + resolved := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if resolved == nil { + return nil + } + serviceType := resolved + if resolved.Kind == ast.KindAnyKeyword { + serviceType = tx.Factory().NewKeywordTypeNode(ast.KindNeverKeyword) + } + return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier(name), nil, serviceType) +} + +func getEffectSchemaBaseModelName(statement *ast.Node) string { + if !ast.IsVariableStatement(statement) || statement.AsVariableStatement().DeclarationList == nil { + return "" + } + declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes + if len(declarations) != 1 || declarations[0].Name() == nil || !ast.IsIdentifier(declarations[0].Name()) { + return "" + } + name := declarations[0].Name().Text() + if !strings.HasSuffix(name, "_base") { + return "" + } + baseName := strings.TrimSuffix(name, "_base") + return strings.TrimPrefix(baseName, "__") +} + +func (tx *DeclarationTransformer) updateEffectSchemaBaseDeclaration(statement *ast.Node, modelName string, classDeclaration *ast.Node, usesIntermediateClass bool) *ast.Node { + declaration := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] + name := declaration.Name() + if usesIntermediateClass { + name = tx.Factory().NewIdentifier("__" + modelName + "_base") + } + updatedDeclaration := tx.Factory().UpdateVariableDeclaration( + declaration.AsVariableDeclaration(), + name, + declaration.AsVariableDeclaration().ExclamationToken, + tx.createEffectSchemaFacadeBaseType(modelName, classDeclaration, declaration.AsVariableDeclaration().Type), + declaration.AsVariableDeclaration().Initializer, + ) + declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) + declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) + return tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) +} + +func getEffectSchemaRequestBaseInfo(statement *ast.Node) (effectSchemaRequestBaseInfo, bool) { + if !ast.IsVariableStatement(statement) || statement.AsVariableStatement().DeclarationList == nil { + return effectSchemaRequestBaseInfo{}, false + } + declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes + if len(declarations) != 1 || declarations[0].Name() == nil || !ast.IsIdentifier(declarations[0].Name()) || declarations[0].Type() == nil { + return effectSchemaRequestBaseInfo{}, false + } + modelName := getEffectSchemaBaseModelName(statement) + if modelName == "" { + return effectSchemaRequestBaseInfo{}, false + } + opaqueType := getEffectSchemaRequestOpaqueType(declarations[0].Type(), modelName) + if opaqueType == nil || opaqueType.AsTypeReferenceNode().TypeArguments == nil || len(opaqueType.AsTypeReferenceNode().TypeArguments.Nodes) < 4 { + return effectSchemaRequestBaseInfo{}, false + } + return effectSchemaRequestBaseInfo{modelName: modelName, brand: opaqueType.AsTypeReferenceNode().TypeArguments.Nodes[3]}, true +} + +func getEffectSchemaRequestOpaqueType(typeNode *ast.Node, modelName string) *ast.Node { + if isEffectSchemaRequestOpaqueType(typeNode, modelName) { + return typeNode + } + if typeNode == nil || typeNode.Kind != ast.KindIntersectionType { + return nil + } + for _, part := range typeNode.AsIntersectionTypeNode().Types.Nodes { + if isEffectSchemaRequestOpaqueType(part, modelName) { + return part + } + } + return nil +} + +func isEffectSchemaRequestOpaqueType(typeNode *ast.Node, modelName string) bool { + if typeNode == nil || typeNode.Kind != ast.KindTypeReference { + return false + } + typeName := typeNode.AsTypeReferenceNode().TypeName + if typeName == nil || typeName.Kind != ast.KindQualifiedName { + return false + } + qualifiedName := typeName.AsQualifiedName() + if qualifiedName.Right.Text() != "Opaque" || qualifiedName.Left == nil || qualifiedName.Left.Kind != ast.KindIdentifier || qualifiedName.Left.Text() != "S" { + return false + } + typeArguments := typeNode.AsTypeReferenceNode().TypeArguments + if typeArguments == nil || len(typeArguments.Nodes) < 4 { + return false + } + return isEffectSchemaNamedTypeReference(typeArguments.Nodes[0], modelName) && isEffectSchemaExtendedSchemaNoEncodedType(typeArguments.Nodes[1]) +} + +func isEffectSchemaNamedTypeReference(typeNode *ast.Node, name string) bool { + return typeNode != nil && typeNode.Kind == ast.KindTypeReference && typeNode.AsTypeReferenceNode().TypeName.Kind == ast.KindIdentifier && typeNode.AsTypeReferenceNode().TypeName.Text() == name +} + +func isEffectSchemaExtendedSchemaNoEncodedType(typeNode *ast.Node) bool { + if typeNode == nil || typeNode.Kind != ast.KindTypeQuery { + return false + } + exprName := typeNode.AsTypeQueryNode().ExprName + if exprName == nil || exprName.Kind != ast.KindQualifiedName { + return false + } + qualifiedName := exprName.AsQualifiedName() + return qualifiedName.Right.Text() == "ExtendedSchemaNoEncoded" && qualifiedName.Left != nil && qualifiedName.Left.Kind == ast.KindIdentifier && qualifiedName.Left.Text() == "S" +} + +func (tx *DeclarationTransformer) updateEffectSchemaRequestBaseDeclaration(statement *ast.Node, info effectSchemaRequestBaseInfo) *ast.Node { + declaration := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] + updatedType := tx.updateEffectSchemaRequestBaseType(declaration.Type(), info) + if updatedType == nil { + return nil + } + updatedDeclaration := tx.Factory().UpdateVariableDeclaration( + declaration.AsVariableDeclaration(), + declaration.Name(), + declaration.AsVariableDeclaration().ExclamationToken, + updatedType, + declaration.AsVariableDeclaration().Initializer, + ) + declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) + declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) + return tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) +} + +func (tx *DeclarationTransformer) updateEffectSchemaRequestBaseType(typeNode *ast.Node, info effectSchemaRequestBaseInfo) *ast.Node { + if isEffectSchemaRequestOpaqueType(typeNode, info.modelName) { + return tx.createEffectSchemaFacadeTypeReference(info.modelName, info.brand, "OpaqueFacade") + } + if typeNode == nil || typeNode.Kind != ast.KindIntersectionType { + return nil + } + changed := false + types := make([]*ast.Node, 0, len(typeNode.AsIntersectionTypeNode().Types.Nodes)) + for _, part := range typeNode.AsIntersectionTypeNode().Types.Nodes { + if isEffectSchemaRequestOpaqueType(part, info.modelName) { + changed = true + types = append(types, tx.createEffectSchemaFacadeTypeReference(info.modelName, info.brand, "OpaqueFacade")) + } else { + types = append(types, part) + } + } + if !changed { + return nil + } + return tx.Factory().UpdateIntersectionTypeNode(typeNode.AsIntersectionTypeNode(), tx.Factory().NewNodeList(types)) +} + +func needsEffectSchemaIntermediateClass(classDeclaration *ast.Node) bool { + return classDeclaration != nil && classDeclaration.ClassLikeData() != nil && len(classDeclaration.ClassLikeData().Members.Nodes) > 0 +} + +func (tx *DeclarationTransformer) createEffectSchemaIntermediateClass(modelName string) *ast.Node { + return tx.Factory().NewClassDeclaration( + tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindDeclareKeyword)}), + tx.Factory().NewIdentifier("__"+modelName), + nil, + tx.Factory().NewNodeList([]*ast.Node{ + tx.Factory().NewHeritageClause(ast.KindExtendsKeyword, tx.Factory().NewNodeList([]*ast.Node{ + tx.Factory().NewExpressionWithTypeArguments(tx.Factory().NewIdentifier("__"+modelName+"_base"), nil), + })), + }), + tx.Factory().NewNodeList([]*ast.Node{}), + ) +} + +func (tx *DeclarationTransformer) updateEffectSchemaClassDeclaration(classDeclaration *ast.Node, modelName string) *ast.Node { + return tx.Factory().UpdateClassDeclaration( + classDeclaration.AsClassDeclaration(), + classDeclaration.Modifiers(), + classDeclaration.Name(), + classDeclaration.AsClassDeclaration().TypeParameters, + tx.Factory().NewNodeList([]*ast.Node{ + tx.Factory().NewHeritageClause(ast.KindExtendsKeyword, tx.Factory().NewNodeList([]*ast.Node{ + tx.Factory().NewExpressionWithTypeArguments(tx.Factory().NewIdentifier("__"+modelName), nil), + })), + }), + classDeclaration.AsClassDeclaration().Members, + ) +} + +func (tx *DeclarationTransformer) createEffectSchemaFacadeBaseType(modelName string, classDeclaration *ast.Node, baseType *ast.Node) *ast.Node { + facadeName := getEffectSchemaClassFacadeName(classDeclaration) + brandType := tx.getEffectSchemaFacadeBrandType(baseType, facadeName) + return tx.Factory().NewIntersectionTypeNode(tx.Factory().NewNodeList([]*ast.Node{ + tx.createEffectSchemaFacadeTypeReference(modelName, brandType, facadeName), + tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList(tx.createEffectSchemaStaticMembers(classDeclaration, modelName))), + })) +} + +// The facade's Brand (last type arg). For the class/error families the source base is +// S.EnhancedClass — the 3rd arg is the brand (e.g. Cause.YieldableError +// for errors); preserve it. The Opaque family carries no brand on the base, so use {}. +func (tx *DeclarationTransformer) getEffectSchemaFacadeBrandType(baseType *ast.Node, facadeName string) *ast.Node { + empty := tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList([]*ast.Node{})) + if facadeName == "OpaqueFacade" || baseType == nil { + return empty + } + typeNode := baseType + if ast.IsIntersectionTypeNode(typeNode) { + nodes := typeNode.AsIntersectionTypeNode().Types.Nodes + if len(nodes) == 0 { + return empty + } + typeNode = nodes[0] + } + if typeNode.Kind != ast.KindTypeReference || typeNode.AsTypeReferenceNode().TypeArguments == nil { + return empty + } + args := typeNode.AsTypeReferenceNode().TypeArguments.Nodes + if len(args) >= 3 { + return args[2] + } + return empty +} + +func (tx *DeclarationTransformer) createEffectSchemaFacadeTypeReference(modelName string, brandType *ast.Node, facadeName string) *ast.Node { + model := tx.Factory().NewIdentifier(modelName) + return tx.Factory().NewTypeReferenceNode( + tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier("S"), tx.Factory().NewIdentifier(facadeName)), + tx.Factory().NewNodeList([]*ast.Node{ + tx.Factory().NewTypeReferenceNode(model, nil), + tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("Encoded")), nil), + tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("Make")), nil), + tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("DecodingServices")), nil), + tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("EncodingServices")), nil), + brandType, + }), + ) +} + +func (tx *DeclarationTransformer) createEffectSchemaStaticMembers(classDeclaration *ast.Node, modelName string) []*ast.Node { + members := []*ast.Node{} + hasFields := tx.createEffectSchemaFieldsDeclaration(classDeclaration) != nil + // NOTE: `identifier` (generic `string`) is intentionally NOT emitted here — it lives on the + // facade interfaces (OpaqueFacade/OpaqueClassFacade/OpaqueErrorFacadeClass) in effect-app. + // Only per-model, precisely-typed statics belong here. + tx.addSchemaStaticMember(&members, classDeclaration, "fields", true, core.IfElse(hasFields, modelName, "")) + tx.addSchemaStaticMember(&members, classDeclaration, "mapFields", false, core.IfElse(hasFields, modelName, "")) + tx.addSchemaStaticMember(&members, classDeclaration, "to", true, "") + tx.addSchemaStaticMember(&members, classDeclaration, "from", true, "") + tx.addSchemaStaticMember(&members, classDeclaration, "copy", true, modelName) + return members +} + +func (tx *DeclarationTransformer) createEffectSchemaNamedMemberReference(modelName string, memberName string) *ast.Node { + return tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(modelName), tx.Factory().NewIdentifier(memberName)), nil) +} + +func (tx *DeclarationTransformer) rewriteEffectSchemaStaticMemberType(typeNode *ast.Node, name string, modelName string) *ast.Node { + switch name { + case "fields": + return tx.createEffectSchemaNamedMemberReference(modelName, "Fields") + case "mapFields": + return tx.rewriteEffectSchemaMapFieldsType(typeNode, modelName) + case "copy": + return tx.rewriteEffectSchemaCopyType(typeNode, modelName) + default: + return typeNode + } +} + +func (tx *DeclarationTransformer) rewriteEffectSchemaMapFieldsType(typeNode *ast.Node, modelName string) *ast.Node { + if typeNode == nil || typeNode.Kind != ast.KindFunctionType { + return typeNode + } + mapFieldsType := typeNode.AsFunctionTypeNode() + if mapFieldsType.Parameters == nil || len(mapFieldsType.Parameters.Nodes) == 0 { + return typeNode + } + callbackParameter := mapFieldsType.Parameters.Nodes[0].AsParameterDeclaration() + callbackType := callbackParameter.Type + if callbackType == nil || callbackType.Kind != ast.KindFunctionType { + return typeNode + } + callbackFunctionType := callbackType.AsFunctionTypeNode() + if callbackFunctionType.Parameters == nil || len(callbackFunctionType.Parameters.Nodes) == 0 { + return typeNode + } + fieldsParameter := callbackFunctionType.Parameters.Nodes[0].AsParameterDeclaration() + updatedFieldsParameter := tx.Factory().UpdateParameterDeclaration( + fieldsParameter, + fieldsParameter.Modifiers(), + fieldsParameter.DotDotDotToken, + fieldsParameter.Name(), + fieldsParameter.QuestionToken, + tx.createEffectSchemaNamedMemberReference(modelName, "Fields"), + fieldsParameter.Initializer, + ) + updatedCallbackParameters := append([]*ast.Node{updatedFieldsParameter}, callbackFunctionType.Parameters.Nodes[1:]...) + updatedCallbackType := tx.Factory().UpdateFunctionTypeNode( + callbackFunctionType, + callbackFunctionType.TypeParameters, + tx.Factory().NewNodeList(updatedCallbackParameters), + callbackFunctionType.Type, + ) + updatedCallbackParameter := tx.Factory().UpdateParameterDeclaration( + callbackParameter, + callbackParameter.Modifiers(), + callbackParameter.DotDotDotToken, + callbackParameter.Name(), + callbackParameter.QuestionToken, + updatedCallbackType, + callbackParameter.Initializer, + ) + updatedMapFieldsParameters := append([]*ast.Node{updatedCallbackParameter}, mapFieldsType.Parameters.Nodes[1:]...) + return tx.Factory().UpdateFunctionTypeNode( + mapFieldsType, + mapFieldsType.TypeParameters, + tx.Factory().NewNodeList(updatedMapFieldsParameters), + mapFieldsType.Type, + ) +} + +func (tx *DeclarationTransformer) rewriteEffectSchemaCopyType(typeNode *ast.Node, modelName string) *ast.Node { + model := tx.Factory().NewTypeReferenceNode(tx.Factory().NewIdentifier(modelName), nil) + if ast.IsImportTypeNode(typeNode) { + importType := typeNode.AsImportTypeNode() + if importType.Qualifier != nil && rightmostEntityNameText(importType.Qualifier) == "StructuralCopyOrigin" { + return tx.Factory().UpdateImportTypeNode(importType, importType.IsTypeOf, importType.Argument, importType.Attributes, importType.Qualifier, tx.Factory().NewNodeList([]*ast.Node{model})) + } + return typeNode + } + if ast.IsTypeReferenceNode(typeNode) && rightmostEntityNameText(typeNode.AsTypeReferenceNode().TypeName) == "StructuralCopyOrigin" { + return tx.Factory().UpdateTypeReferenceNode(typeNode.AsTypeReferenceNode(), typeNode.AsTypeReferenceNode().TypeName, tx.Factory().NewNodeList([]*ast.Node{model})) + } + return typeNode +} + +func rightmostEntityNameText(name *ast.Node) string { + if ast.IsIdentifier(name) { + return name.Text() + } + if ast.IsQualifiedName(name) { + return name.AsQualifiedName().Right.Text() + } + return "" +} + +func (tx *DeclarationTransformer) addSchemaStaticMember(members *[]*ast.Node, classDeclaration *ast.Node, name string, readonly bool, modelName string) { + typeNode := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if typeNode == nil || typeNode.Kind == ast.KindAnyKeyword { + return + } + var modifiers *ast.ModifierList + if readonly { + modifiers = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindReadonlyKeyword)}) + } + if modelName != "" { + typeNode = tx.rewriteEffectSchemaStaticMemberType(typeNode, name, modelName) + } + *members = append(*members, tx.Factory().NewPropertySignatureDeclaration(modifiers, tx.Factory().NewIdentifier(name), nil, typeNode, nil)) +} + func (tx *DeclarationTransformer) ensureModifiers(node *ast.Node) *ast.ModifierList { currentFlags := ast.GetCombinedModifierFlags(tx.EmitContext().ParseNode(node)) & ast.ModifierFlagsAll newFlags := tx.ensureModifierFlags(node) diff --git a/internal/transformers/declarations/transform_test.go b/internal/transformers/declarations/transform_test.go new file mode 100644 index 00000000000..9ad39a7ce4c --- /dev/null +++ b/internal/transformers/declarations/transform_test.go @@ -0,0 +1,60 @@ +package declarations + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/packagejson" +) + +func TestPackageJsonReferencesEffectApp(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + json string + want bool + }{ + { + name: "effect-app dependency", + json: `{"dependencies":{"effect-app":"latest"}}`, + want: true, + }, + { + name: "effect-app scoped dependency", + json: `{"devDependencies":{"@effect-app/core":"latest"}}`, + want: true, + }, + { + name: "effect without effect-app", + json: `{"dependencies":{"effect":"latest"}}`, + want: false, + }, + { + name: "empty package", + json: `{}`, + want: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + fields, err := packagejson.Parse([]byte(test.json)) + if err != nil { + t.Fatal(err) + } + got := packageJsonReferencesEffectApp(&packagejson.PackageJson{Fields: fields}) + if got != test.want { + t.Fatalf("packageJsonReferencesEffectApp() = %v, want %v", got, test.want) + } + }) + } +} + +func TestPackageJsonReferencesEffectAppNil(t *testing.T) { + t.Parallel() + + if packageJsonReferencesEffectApp(nil) { + t.Fatal("nil package.json should not reference effect-app") + } +} diff --git a/internal/tsoptions/declscompiler.go b/internal/tsoptions/declscompiler.go index cde4e2556bc..f56d9d819d2 100644 --- a/internal/tsoptions/declscompiler.go +++ b/internal/tsoptions/declscompiler.go @@ -202,6 +202,14 @@ var commonOptionsWithBuild = []*CommandLineOption{ transpileOptionValue: core.TSUnknown, DefaultValueDescription: false, }, + { + Name: "disableEffectAppDtsFacades", + Kind: CommandLineOptionTypeBoolean, + Category: diagnostics.Emit, + Description: diagnostics.Disable_effect_app_declaration_facade_emit, + DefaultValueDescription: false, + AffectsEmit: true, + }, { Name: "assumeChangesOnlyAffectDirectDependencies", Kind: CommandLineOptionTypeBoolean, diff --git a/internal/tsoptions/parsinghelpers.go b/internal/tsoptions/parsinghelpers.go index 47a4d476bf4..4bc39103ddb 100644 --- a/internal/tsoptions/parsinghelpers.go +++ b/internal/tsoptions/parsinghelpers.go @@ -242,6 +242,8 @@ func parseCompilerOptions(key string, value any, allOptions *core.CompilerOption allOptions.DeduplicatePackages = ParseTristate(value) case "diagnostics": allOptions.Diagnostics = ParseTristate(value) + case "disableEffectAppDtsFacades": + allOptions.DisableEffectAppDtsFacades = ParseTristate(value) case "disableSizeLimit": allOptions.DisableSizeLimit = ParseTristate(value) case "disableSourceOfProjectReferenceRedirect": diff --git a/testdata/baselines/reference/tsbuild/commandLine/help.js b/testdata/baselines/reference/tsbuild/commandLine/help.js index 37d3b89b958..b9b4bd237f5 100644 --- a/testdata/baselines/reference/tsbuild/commandLine/help.js +++ b/testdata/baselines/reference/tsbuild/commandLine/help.js @@ -114,6 +114,11 @@ Disable emitting files from a compilation. type: boolean default: false +--disableEffectAppDtsFacades +Disable effect-app declaration facade emit. +type: boolean +default: false + --assumeChangesOnlyAffectDirectDependencies Have recompiles in projects that use 'incremental' and 'watch' mode assume that changes within a file will only affect files directly depending on it. type: boolean diff --git a/testdata/baselines/reference/tsbuild/commandLine/locale.js b/testdata/baselines/reference/tsbuild/commandLine/locale.js index bbdd99f5836..16d25f88e4e 100644 --- a/testdata/baselines/reference/tsbuild/commandLine/locale.js +++ b/testdata/baselines/reference/tsbuild/commandLine/locale.js @@ -114,6 +114,11 @@ Disable emitting files from a compilation. type: boolean default: false +--disableEffectAppDtsFacades +Disable effect-app declaration facade emit. +type: boolean +default: false + --assumeChangesOnlyAffectDirectDependencies Have recompiles in projects that use 'incremental' and 'watch' mode assume that changes within a file will only affect files directly depending on it. type: boolean diff --git a/testdata/baselines/reference/tsc/commandLine/help-all.js b/testdata/baselines/reference/tsc/commandLine/help-all.js index 4e0012b2d8c..2712b5d74fb 100644 --- a/testdata/baselines/reference/tsc/commandLine/help-all.js +++ b/testdata/baselines/reference/tsc/commandLine/help-all.js @@ -370,6 +370,11 @@ Create sourcemaps for d.ts files. type: boolean default: false +--disableEffectAppDtsFacades +Disable effect-app declaration facade emit. +type: boolean +default: false + --downlevelIteration Emit more compliant, but verbose and less performant JavaScript for iteration. type: boolean