From 1956eae57cdeb2088558c1597f312f4207002588 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:48:26 +0000 Subject: [PATCH 01/23] chore: update generated/latest --- .changeset/readme-absolute-links.md | 5 + README.md | 192 ++++++++++++++-------------- internal/rules/rules_json_test.go | 23 +++- 3 files changed, 123 insertions(+), 97 deletions(-) create mode 100644 .changeset/readme-absolute-links.md diff --git a/.changeset/readme-absolute-links.md b/.changeset/readme-absolute-links.md new file mode 100644 index 00000000..a1a35d7b --- /dev/null +++ b/.changeset/readme-absolute-links.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Use absolute GitHub URLs for links in the published README so documentation links remain clickable on npm. diff --git a/README.md b/README.md index 467db20c..c3922582 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The Effect LSP doubles as a tool to perform type-aware linting of Effect code, a Linting can occur either during the `tsc` typecheck phase (with the benefit of running typechecking only once and caching the output), or via a dedicated `npx @effect/tsgo diagnostics --project tsconfig.json` command (with typechecking occurring again), or via the Oxlint Patch. -See the Oxlint Setup guide for instructions on how to install and configure Oxlint with the Effect LSP. +See the Oxlint Setup guide for instructions on how to install and configure Oxlint with the Effect LSP. When running in `tsc` mode, the Effect diagnostics are emitted as standard TypeScript diagnostics, and can be configured to affect the `tsc` exit code through the options `ignoreEffectSuggestionsInTscExitCode`, `ignoreEffectWarningsInTscExitCode`, and `ignoreEffectErrorsInTscExitCode`. @@ -46,104 +46,104 @@ Some diagnostics are off by default or have a default severity of suggestion, bu Correctness Wrong, unsafe, or structurally invalid code patterns. - anyUnknownInErrorContextDetects 'any' or 'unknown' types in Effect error or requirements channels - classSelfMismatchEnsures Self type parameter matches the class name in Context/Service/Tag/Schema classes - duplicatePackageWarns when multiple versions of an Effect-related package are detected in the program - effectFnImplicitAnyMirrors noImplicitAny for unannotated Effect.fn, Effect.fnUntraced, and Effect.fnUntracedEager callback parameters when no outer contextual function type exists. Requires TS's noImplicitAny: true - floatingEffectDetects Effect values that are neither yielded nor assigned - floatingEffectInVitestDetects Effects returned from non-Effect-aware Vitest callbacks - genericEffectServicesPrevents services with type parameters that cannot be discriminated at runtime - missingEffectContextDetects Effect values with unhandled context requirements - missingEffectErrorDetects Effect values with unhandled error types - missingLayerContextDetects Layer values with unhandled context requirements - missingReturnYieldStarSuggests using return yield* for Effects that never succeed - missingStarInYieldEffectGenDetects bare yield (without *) inside Effect generator scopes - nonObjectEffectServiceTypeEnsures Effect.Service types are objects, not primitives - outdatedApiDetects usage of APIs that have been removed or renamed in Effect v4 - overriddenSchemaConstructorPrevents overriding constructors in Schema classes which breaks decoding behavior - promiseInEffectSuccessDetects Promise types in Effect success channels where they are not awaited - schemaLiteralNonFiniteReports statically known non-finite numbers passed to Schema literal constructors - schemaOpaqueInstanceMemberDisallows instance members in classes extending Schema.Opaque + anyUnknownInErrorContextDetects 'any' or 'unknown' types in Effect error or requirements channels + classSelfMismatchEnsures Self type parameter matches the class name in Context/Service/Tag/Schema classes + duplicatePackageWarns when multiple versions of an Effect-related package are detected in the program + effectFnImplicitAnyMirrors noImplicitAny for unannotated Effect.fn, Effect.fnUntraced, and Effect.fnUntracedEager callback parameters when no outer contextual function type exists. Requires TS's noImplicitAny: true + floatingEffectDetects Effect values that are neither yielded nor assigned + floatingEffectInVitestDetects Effects returned from non-Effect-aware Vitest callbacks + genericEffectServicesPrevents services with type parameters that cannot be discriminated at runtime + missingEffectContextDetects Effect values with unhandled context requirements + missingEffectErrorDetects Effect values with unhandled error types + missingLayerContextDetects Layer values with unhandled context requirements + missingReturnYieldStarSuggests using return yield* for Effects that never succeed + missingStarInYieldEffectGenDetects bare yield (without *) inside Effect generator scopes + nonObjectEffectServiceTypeEnsures Effect.Service types are objects, not primitives + outdatedApiDetects usage of APIs that have been removed or renamed in Effect v4 + overriddenSchemaConstructorPrevents overriding constructors in Schema classes which breaks decoding behavior + promiseInEffectSuccessDetects Promise types in Effect success channels where they are not awaited + schemaLiteralNonFiniteReports statically known non-finite numbers passed to Schema literal constructors + schemaOpaqueInstanceMemberDisallows instance members in classes extending Schema.Opaque Anti-pattern Discouraged patterns that often lead to bugs or confusing behavior. - catchUnfailableEffectWarns when using error handling on Effects that never fail - effectFnIifeEffect.fn or Effect.fnUntraced is called as an IIFE; use Effect.gen instead - effectGenUsesAdapterWarns when using the deprecated adapter parameter in Effect.gen - effectInFailureWarns when an Effect is used inside an Effect failure channel - effectInVoidSuccessDetects nested Effects in void success channels that may cause unexecuted effects - globalErrorInEffectCatchWarns when catch callbacks return global Error type instead of typed errors - globalErrorInEffectFailureWarns when the global Error type is used in an Effect failure channel - layerMergeAllWithDependenciesDetects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires - lazyEffectSuggests avoiding exported zero-argument functions and service members that lazily return Effect or Stream values - lazyPromiseInEffectSyncWarns when Effect.sync lazily returns a Promise instead of using an async Effect constructor - leakingRequirementsDetects implementation services leaked in service methods - multipleEffectProvideWarns against chaining Effect.provide calls which can cause service lifecycle issues - preferUnsafeConstructorSuggests replacing Effect.runSync of a pure effect constructor with the synchronous *Unsafe variant exported by the same module - returnEffectInGenWarns when returning an Effect in a generator causes nested Effect<Effect<...>> - runEffectInsideEffectSuggests using Runtime or Effect.run*With methods instead of Effect.run* inside Effect contexts - schemaSyncInEffectSuggests using Effect-based Schema methods instead of sync methods inside Effect generators - scopeInLayerEffectSuggests using Layer.scoped instead of Layer.effect when Scope is in requirements - strictEffectProvideWarns when using Effect.provide with layers outside of application entry points - tryCatchInEffectGenDiscourages try/catch in Effect generators in favor of Effect error handling - unknownInEffectCatchWarns when catch callbacks return unknown instead of typed errors + catchUnfailableEffectWarns when using error handling on Effects that never fail + effectFnIifeEffect.fn or Effect.fnUntraced is called as an IIFE; use Effect.gen instead + effectGenUsesAdapterWarns when using the deprecated adapter parameter in Effect.gen + effectInFailureWarns when an Effect is used inside an Effect failure channel + effectInVoidSuccessDetects nested Effects in void success channels that may cause unexecuted effects + globalErrorInEffectCatchWarns when catch callbacks return global Error type instead of typed errors + globalErrorInEffectFailureWarns when the global Error type is used in an Effect failure channel + layerMergeAllWithDependenciesDetects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires + lazyEffectSuggests avoiding exported zero-argument functions and service members that lazily return Effect or Stream values + lazyPromiseInEffectSyncWarns when Effect.sync lazily returns a Promise instead of using an async Effect constructor + leakingRequirementsDetects implementation services leaked in service methods + multipleEffectProvideWarns against chaining Effect.provide calls which can cause service lifecycle issues + preferUnsafeConstructorSuggests replacing Effect.runSync of a pure effect constructor with the synchronous *Unsafe variant exported by the same module + returnEffectInGenWarns when returning an Effect in a generator causes nested Effect<Effect<...>> + runEffectInsideEffectSuggests using Runtime or Effect.run*With methods instead of Effect.run* inside Effect contexts + schemaSyncInEffectSuggests using Effect-based Schema methods instead of sync methods inside Effect generators + scopeInLayerEffectSuggests using Layer.scoped instead of Layer.effect when Scope is in requirements + strictEffectProvideWarns when using Effect.provide with layers outside of application entry points + tryCatchInEffectGenDiscourages try/catch in Effect generators in favor of Effect error handling + unknownInEffectCatchWarns when catch callbacks return unknown instead of typed errors Effect-native Prefer Effect-native APIs and abstractions when available. - abortControllerInEffectWarns when manually constructing AbortController inside Effect generators instead of using Effect.abortSignal - asyncFunctionWarns when declaring async functions and suggests using Effect values and Effect.gen for async control flow - cryptoRandomUUIDWarns when using crypto.randomUUID() outside Effect generators instead of the Effect Random module, which uses Effect-injected randomness rather than the crypto module behind the scenes - cryptoRandomUUIDInEffectWarns when using crypto.randomUUID() inside Effect generators instead of the Effect Random module, which uses Effect-injected randomness rather than the crypto module behind the scenes - extendsNativeErrorWarns when a class directly extends the native Error class - globalConsoleWarns when using console methods outside Effect generators instead of Effect.log/Logger - globalConsoleInEffectWarns when using console methods inside Effect generators instead of Effect.log/Logger - globalDateWarns when using Date.now() or new Date() outside Effect generators instead of Clock/DateTime - globalDateInEffectWarns when using Date.now() or new Date() inside Effect generators instead of Clock/DateTime - globalFetchWarns when using the global fetch function outside Effect generators instead of the Effect HTTP client - globalFetchInEffectWarns when using the global fetch function inside Effect generators instead of the Effect HTTP client - globalRandomWarns when using Math.random() outside Effect generators instead of the Random service - globalRandomInEffectWarns when using Math.random() inside Effect generators instead of the Random service - globalTimersWarns when using setTimeout/setInterval outside Effect generators instead of Effect.sleep/Schedule - globalTimersInEffectWarns when using setTimeout/setInterval inside Effect generators instead of Effect.sleep/Schedule - instanceOfSchemaSuggests using Schema.is instead of instanceof for Effect Schema types - newPromiseWarns when constructing promises with new Promise instead of using Effect APIs - nodeBuiltinImportWarns when importing Node.js built-in modules that have Effect-native counterparts - preferSchemaOverJsonSuggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify - processEnvWarns when reading process.env outside Effect generators instead of using Effect Config - processEnvInEffectWarns when reading process.env inside Effect generators instead of using Effect Config - unsafeEffectTypeAssertionDetects unsafe type assertions that narrow Effect, Stream, or Layer error or requirements channels + abortControllerInEffectWarns when manually constructing AbortController inside Effect generators instead of using Effect.abortSignal + asyncFunctionWarns when declaring async functions and suggests using Effect values and Effect.gen for async control flow + cryptoRandomUUIDWarns when using crypto.randomUUID() outside Effect generators instead of the Effect Random module, which uses Effect-injected randomness rather than the crypto module behind the scenes + cryptoRandomUUIDInEffectWarns when using crypto.randomUUID() inside Effect generators instead of the Effect Random module, which uses Effect-injected randomness rather than the crypto module behind the scenes + extendsNativeErrorWarns when a class directly extends the native Error class + globalConsoleWarns when using console methods outside Effect generators instead of Effect.log/Logger + globalConsoleInEffectWarns when using console methods inside Effect generators instead of Effect.log/Logger + globalDateWarns when using Date.now() or new Date() outside Effect generators instead of Clock/DateTime + globalDateInEffectWarns when using Date.now() or new Date() inside Effect generators instead of Clock/DateTime + globalFetchWarns when using the global fetch function outside Effect generators instead of the Effect HTTP client + globalFetchInEffectWarns when using the global fetch function inside Effect generators instead of the Effect HTTP client + globalRandomWarns when using Math.random() outside Effect generators instead of the Random service + globalRandomInEffectWarns when using Math.random() inside Effect generators instead of the Random service + globalTimersWarns when using setTimeout/setInterval outside Effect generators instead of Effect.sleep/Schedule + globalTimersInEffectWarns when using setTimeout/setInterval inside Effect generators instead of Effect.sleep/Schedule + instanceOfSchemaSuggests using Schema.is instead of instanceof for Effect Schema types + newPromiseWarns when constructing promises with new Promise instead of using Effect APIs + nodeBuiltinImportWarns when importing Node.js built-in modules that have Effect-native counterparts + preferSchemaOverJsonSuggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify + processEnvWarns when reading process.env outside Effect generators instead of using Effect Config + processEnvInEffectWarns when reading process.env inside Effect generators instead of using Effect Config + unsafeEffectTypeAssertionDetects unsafe type assertions that narrow Effect, Stream, or Layer error or requirements channels Style Cleanup, consistency, and idiomatic Effect code. - catchAllToMapErrorSuggests using Effect.mapError instead of Effect.catch + Effect.fail - catchChainToFirstSuccessOfSuggests Effect.firstSuccessOf for consecutive error-independent Effect.catch fallbacks when the error type is preserved - catchTagToCatchReasonSuggests Effect.catchReason or Effect.catchReasons for handlers that re-fail unmatched reason._tag branches - catchToIgnoreSuggests using Effect.ignore or Effect.ignoreCause instead of Effect.catch/catchCause returning Effect.void - catchToOrElseSucceedSuggests using Effect.orElseSucceed instead of Effect.catch + Effect.succeed - deterministicKeysEnforces deterministic naming for service/tag/error identifiers based on class names - effectDoNotationSuggests using Effect.gen or Effect.fn instead of the Effect.Do notation helpers - effectFnOpportunitySuggests using Effect.fn for functions that return an Effect - effectMapFlattenSuggests using Effect.flatMap instead of Effect.map followed by Effect.flatten in piping flows - effectMapVoidSuggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {}) - effectSucceedWithVoidSuggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0) - flatMapToMapSuggests using Effect.map instead of Effect.flatMap when the callback only wraps its result with Effect.succeed - missedPipeableOpportunitySuggests using .pipe() for nested function calls - missingEffectServiceDependencyChecks that Effect.Service dependencies satisfy all required layer inputs - missingPipeableSignatureReports exported fixed-arity functions whose call signatures have no corresponding pipeable overload - multipleCatchTagSuggests collapsing consecutive Effect.catchTag transformations into a single Effect.catchTags call when semantics stay equivalent - nestedEffectGenYieldWarns when yielding a nested bare Effect.gen inside an existing Effect generator context - newSchemaClassSuggests using Schema make instead of new for Schema classes - preferSchemaTypePropertyDisallows Schema.Schema.Type<typeof X> in favor of typeof X.Type - preferTypedSchemaDecoderSuggests typed Schema decoders when the input is assignable to the schema's Encoded type - redundantMapErrorSuggests hoisting a repeated trailing Effect.mapError from every yield in an Effect generator - redundantOrDieSuggests hoisting a repeated trailing Effect.orDie from every yield in an Effect generator - redundantSchemaTagIdentifierSuggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest - schemaNumberSuggests Schema.Finite and Schema.FiniteFromString instead of Schema.Number APIs when describing domain numbers - schemaStructWithTagSuggests using Schema.TaggedStruct instead of Schema.Struct with _tag field - schemaUnionOfLiteralsSuggests combining multiple Schema.Literal calls in Schema.Union into a single Schema.Literal - serviceNotAsClassWarns when Context.Service is used as a variable instead of a class declaration - strictBooleanExpressionsEnforces boolean types in conditional expressions for type safety - syncToSucceedSuggests using Effect.succeed instead of Effect.sync when the thunk returns a constant value - unnecessaryArrowBlockSuggests using a concise arrow body when the block only returns an expression - unnecessaryEffectGenSuggests removing Effect.gen when it contains only a single return statement - unnecessaryFailYieldableErrorSuggests yielding yieldable errors directly instead of wrapping with Effect.fail - unnecessaryPipeRemoves pipe calls with no arguments - unnecessaryPipeChainSimplifies chained pipe calls into a single pipe call - unnecessaryTypeofTypeSuggests replacing typeof Schema.Type style annotations with the matching named type when available + catchAllToMapErrorSuggests using Effect.mapError instead of Effect.catch + Effect.fail + catchChainToFirstSuccessOfSuggests Effect.firstSuccessOf for consecutive error-independent Effect.catch fallbacks when the error type is preserved + catchTagToCatchReasonSuggests Effect.catchReason or Effect.catchReasons for handlers that re-fail unmatched reason._tag branches + catchToIgnoreSuggests using Effect.ignore or Effect.ignoreCause instead of Effect.catch/catchCause returning Effect.void + catchToOrElseSucceedSuggests using Effect.orElseSucceed instead of Effect.catch + Effect.succeed + deterministicKeysEnforces deterministic naming for service/tag/error identifiers based on class names + effectDoNotationSuggests using Effect.gen or Effect.fn instead of the Effect.Do notation helpers + effectFnOpportunitySuggests using Effect.fn for functions that return an Effect + effectMapFlattenSuggests using Effect.flatMap instead of Effect.map followed by Effect.flatten in piping flows + effectMapVoidSuggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {}) + effectSucceedWithVoidSuggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0) + flatMapToMapSuggests using Effect.map instead of Effect.flatMap when the callback only wraps its result with Effect.succeed + missedPipeableOpportunitySuggests using .pipe() for nested function calls + missingEffectServiceDependencyChecks that Effect.Service dependencies satisfy all required layer inputs + missingPipeableSignatureReports exported fixed-arity functions whose call signatures have no corresponding pipeable overload + multipleCatchTagSuggests collapsing consecutive Effect.catchTag transformations into a single Effect.catchTags call when semantics stay equivalent + nestedEffectGenYieldWarns when yielding a nested bare Effect.gen inside an existing Effect generator context + newSchemaClassSuggests using Schema make instead of new for Schema classes + preferSchemaTypePropertyDisallows Schema.Schema.Type<typeof X> in favor of typeof X.Type + preferTypedSchemaDecoderSuggests typed Schema decoders when the input is assignable to the schema's Encoded type + redundantMapErrorSuggests hoisting a repeated trailing Effect.mapError from every yield in an Effect generator + redundantOrDieSuggests hoisting a repeated trailing Effect.orDie from every yield in an Effect generator + redundantSchemaTagIdentifierSuggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest + schemaNumberSuggests Schema.Finite and Schema.FiniteFromString instead of Schema.Number APIs when describing domain numbers + schemaStructWithTagSuggests using Schema.TaggedStruct instead of Schema.Struct with _tag field + schemaUnionOfLiteralsSuggests combining multiple Schema.Literal calls in Schema.Union into a single Schema.Literal + serviceNotAsClassWarns when Context.Service is used as a variable instead of a class declaration + strictBooleanExpressionsEnforces boolean types in conditional expressions for type safety + syncToSucceedSuggests using Effect.succeed instead of Effect.sync when the thunk returns a constant value + unnecessaryArrowBlockSuggests using a concise arrow body when the block only returns an expression + unnecessaryEffectGenSuggests removing Effect.gen when it contains only a single return statement + unnecessaryFailYieldableErrorSuggests yielding yieldable errors directly instead of wrapping with Effect.fail + unnecessaryPipeRemoves pipe calls with no arguments + unnecessaryPipeChainSimplifies chained pipe calls into a single pipe call + unnecessaryTypeofTypeSuggests replacing typeof Schema.Type style annotations with the matching named type when available diff --git a/internal/rules/rules_json_test.go b/internal/rules/rules_json_test.go index 53d8fe60..43a02524 100644 --- a/internal/rules/rules_json_test.go +++ b/internal/rules/rules_json_test.go @@ -89,6 +89,27 @@ func TestReadmeTable(t *testing.T) { } } +func TestReadmeLinksAreAbsolute(t *testing.T) { + t.Parallel() + readmePath := filepath.Join(repoRoot(t), "README.md") + content, err := os.ReadFile(readmePath) + if err != nil { + t.Fatalf("read README.md: %v", err) + } + + patterns := []*regexp.Regexp{ + regexp.MustCompile(`href="([^"]+)"`), + regexp.MustCompile(`\[[^\]]+\]\(([^)]+)\)`), + } + for _, pattern := range patterns { + for _, match := range pattern.FindAllStringSubmatch(string(content), -1) { + if !strings.HasPrefix(match[1], "https://") && !strings.HasPrefix(match[1], "http://") { + t.Errorf("README.md contains non-absolute link %q", match[1]) + } + } + } +} + func TestMetadataJSON(t *testing.T) { t.Parallel() root := repoRoot(t) @@ -737,7 +758,7 @@ func generateReadmeTable() string { lines = append(lines, fmt.Sprintf(" %s %s", html.EscapeString(group.Name), html.EscapeString(group.Description))) for _, r := range groupRules { - lines = append(lines, fmt.Sprintf(" %s%s", + lines = append(lines, fmt.Sprintf(" %s%s", kebabCase(r.name), html.EscapeString(r.name), html.EscapeString(r.description))) } } From ca69e07b416052f79d50874d3b9778b567bc6d11 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:24:59 +0000 Subject: [PATCH 02/23] chore: update generated/latest --- .changeset/deadcode-lint.md | 5 ++ .github/workflows/validate.yml | 8 +++ _tools/deadcode-allow.txt | 7 ++ _tools/repoctl/src/lint.ts | 78 +++++++++++++++++++++++ _tools/repoctl/src/main.ts | 6 ++ _tools/repoctl/test/lint.test.ts | 24 +++++++ etslshooks/init.go | 12 ---- internal/bundledeffect/effect.go | 6 -- internal/codefixes/disable_diagnostics.go | 44 ------------- internal/codegen/codegen.go | 10 --- internal/codegens/codegens.go | 5 -- internal/completions/completions.go | 10 --- internal/effectconfigraw/hooks.go | 19 ------ internal/rewriter/tracker.go | 37 ----------- internal/schemagen/schemagen.go | 12 +--- internal/schemagen/structural.go | 7 +- internal/typeparser/schema_type.go | 14 ---- package.json | 2 +- 18 files changed, 131 insertions(+), 175 deletions(-) create mode 100644 .changeset/deadcode-lint.md create mode 100644 _tools/deadcode-allow.txt create mode 100644 _tools/repoctl/src/lint.ts create mode 100644 _tools/repoctl/test/lint.test.ts delete mode 100644 internal/codefixes/disable_diagnostics.go diff --git a/.changeset/deadcode-lint.md b/.changeset/deadcode-lint.md new file mode 100644 index 00000000..13187233 --- /dev/null +++ b/.changeset/deadcode-lint.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Remove unused internal helpers and add dead-code analysis to the repository lint workflow. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 58d3eda2..3577d61c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -51,5 +51,13 @@ jobs: if: matrix.repoctl run: pnpm run check:repoctl && pnpm run test:repoctl + - name: Install golangci-lint + if: matrix.repoctl + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.9.0 + + - name: Lint + if: matrix.repoctl + run: pnpm lint + - name: Test run: pnpm exec repoctl test diff --git a/_tools/deadcode-allow.txt b/_tools/deadcode-allow.txt new file mode 100644 index 00000000..3fc740c2 --- /dev/null +++ b/_tools/deadcode-allow.txt @@ -0,0 +1,7 @@ +# Entries ending in / match finding path prefixes; other entries match exact function names. + +# Public Go API consumed by integrations outside the tsgo executable. +etsgoapi/ + +# Public adapter consumed by the generated Oxlint integration. +etsoxlintrunner/ diff --git a/_tools/repoctl/src/lint.ts b/_tools/repoctl/src/lint.ts new file mode 100644 index 00000000..4e36207d --- /dev/null +++ b/_tools/repoctl/src/lint.ts @@ -0,0 +1,78 @@ +import * as Console from "effect/Console" +import * as Data from "effect/Data" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Path from "effect/Path" +import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner" +import { CommandError, runCommand, runCommandCaptureSplit } from "./process.ts" + +const deadcodeVersion = "v0.48.0" + +export class DeadCodeError extends Data.TaggedError("DeadCodeError")<{ + readonly findings: ReadonlyArray +}> { + get message(): string { + return [ + ...this.findings, + "", + "deadcode: the functions above are unreachable from the tsgo executable and all tests.", + "Delete them, or add the function name or package path prefix to _tools/deadcode-allow.txt with a reason." + ].join("\n") + } +} + +export const parseDeadCodeAllowlist = (content: string): ReadonlySet => + new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"))) + +export const filterDeadCodeFindings = ( + output: string, + allowlist: ReadonlySet +): ReadonlyArray => + output.split("\n").map((line) => line.trim()).filter((line) => { + const normalizedLine = line.replaceAll("\\", "/") + if (normalizedLine === "" || normalizedLine.includes("_test.go:")) { + return false + } + if ([...allowlist].some((entry) => entry.endsWith("/") && normalizedLine.startsWith(entry))) { + return false + } + const marker = "unreachable func: " + const markerIndex = normalizedLine.indexOf(marker) + return markerIndex === -1 || !allowlist.has(normalizedLine.slice(markerIndex + marker.length)) + }) + +export const runDeadCode = Effect.fnUntraced(function*(repositoryRoot: string) { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const allowlist = parseDeadCodeAllowlist( + yield* fs.readFileString(path.join(repositoryRoot, "_tools", "deadcode-allow.txt")) + ) + const args = [ + "run", + `golang.org/x/tools/cmd/deadcode@${deadcodeVersion}`, + "-test", + "-filter=^github.com/effect-ts/tsgo($|/)", + "./typescript-go/cmd/tsgo", + "./..." + ] + const result = yield* runCommandCaptureSplit("go", repositoryRoot, args, { CGO_ENABLED: "0" }) + if (result.exitCode !== ChildProcessSpawner.ExitCode(0)) { + return yield* new CommandError({ + command: "go", + args, + exitCode: result.exitCode, + stderr: result.stderr + }) + } + + const findings = filterDeadCodeFindings(result.stdout, allowlist) + if (findings.length > 0) { + return yield* new DeadCodeError({ findings }) + } + yield* Console.log("deadcode: clean") +}) + +export const runLint = Effect.fnUntraced(function*(repositoryRoot: string) { + yield* runCommand("golangci-lint", repositoryRoot, ["run", "./..."], false, { CGO_ENABLED: "0" }) + yield* runDeadCode(repositoryRoot) +}) diff --git a/_tools/repoctl/src/main.ts b/_tools/repoctl/src/main.ts index 1fd05986..2e5e82c0 100755 --- a/_tools/repoctl/src/main.ts +++ b/_tools/repoctl/src/main.ts @@ -19,6 +19,7 @@ import { import { ensureEffectFixtures } from "./fixtures.ts" import { updateFlake } from "./flake.ts" import { completeCheck, openPullRequestIfChanged } from "./github.ts" +import { runLint } from "./lint.ts" import { printGeneratedMatrix, printOxlintTestMatrix, @@ -94,6 +95,10 @@ const check = Command.make("check", {}, () => runChecks(repositoryRoot)).pipe( Command.withDescription("Check Go packages followed by the CLI package") ) +const lint = Command.make("lint", {}, () => runLint(repositoryRoot)).pipe( + Command.withDescription("Run Go linters and dead-code analysis") +) + const buildLocalCommand = Command.make("local", {}, () => buildLocal(repositoryRoot)).pipe( Command.withDescription("Build the local Go binary and CLI package") ) @@ -316,6 +321,7 @@ Command.make("repoctl").pipe( codegen, flake, github, + lint, matrix, packages, perf, diff --git a/_tools/repoctl/test/lint.test.ts b/_tools/repoctl/test/lint.test.ts new file mode 100644 index 00000000..404f73e3 --- /dev/null +++ b/_tools/repoctl/test/lint.test.ts @@ -0,0 +1,24 @@ +import assert from "node:assert/strict" +import test from "node:test" +import { filterDeadCodeFindings, parseDeadCodeAllowlist } from "../src/lint.ts" + +test("parses documented dead-code exceptions", () => { + assert.deepEqual( + [...parseDeadCodeAllowlist("# reason\netsgoapi/\n\nOther\n")], + ["etsgoapi/", "Other"] + ) +}) + +test("filters external APIs, tests, and allowed functions", () => { + const findings = filterDeadCodeFindings([ + "etsgoapi/type_parser.go:1:1: unreachable func: PublicAPI", + "etsgoapi\\type_parser.go:2:1: unreachable func: WindowsPublicAPI", + "etsoxlintrunner/runner.go:1:1: unreachable func: RunRule", + "internal/example/example_test.go:1:1: unreachable func: testHelper", + "internal/example/example.go:1:1: unreachable func: Allowed", + "internal/example/example.go:2:1: unreachable func: RemoveMe", + "" + ].join("\n"), new Set(["etsgoapi/", "etsoxlintrunner/", "Allowed"])) + + assert.deepEqual(findings, ["internal/example/example.go:2:1: unreachable func: RemoveMe"]) +}) diff --git a/etslshooks/init.go b/etslshooks/init.go index 121fe951..8314de11 100644 --- a/etslshooks/init.go +++ b/etslshooks/init.go @@ -325,18 +325,6 @@ func formatLayerHover(tp *typeparser.TypeParser, c *checker.Checker, sf *ast.Sou return b.String() } -// formatLayerTypeParams formats Layer type parameters (Provides, Error, Requires). -func formatLayerTypeParams(c *checker.Checker, layer *typeparser.Layer, isMarkdown bool) string { - rOutStr := c.TypeToStringEx(layer.ROut, nil, checker.TypeFormatFlagsNoTruncation, nil) - eStr := c.TypeToStringEx(layer.E, nil, checker.TypeFormatFlagsNoTruncation, nil) - rInStr := c.TypeToStringEx(layer.RIn, nil, checker.TypeFormatFlagsNoTruncation, nil) - - if isMarkdown { - return fmt.Sprintf("```ts\n/* Layer Type Parameters */\ntype Provides = %s\ntype Error = %s\ntype Requires = %s\n```\n", rOutStr, eStr, rInStr) - } - return fmt.Sprintf("Layer Type Parameters:\n Provides = %s\n Error = %s\n Requires = %s\n", rOutStr, eStr, rInStr) -} - // isDeclarationName checks whether the given node is the name node of a variable or property declaration. // This is used to restrict layer hover enrichment to the declaration name only, // not to arbitrary nodes within the initializer expression. diff --git a/internal/bundledeffect/effect.go b/internal/bundledeffect/effect.go index 1d948c8f..de8c6e05 100644 --- a/internal/bundledeffect/effect.go +++ b/internal/bundledeffect/effect.go @@ -47,12 +47,6 @@ func EnsurePackageInstalled(version EffectVersion, packageName string) error { return nil } -func PackageFile(version EffectVersion, packageName string, file string) (string, bool) { - path := pathpkg.Join(string(version), "node_modules", packageName, file) - content, ok := fixtures().files[path] - return string(content), ok -} - type fixtureProfileManifest struct { Requested map[string]string `json:"requested"` Resolved map[string]string `json:"resolved"` diff --git a/internal/codefixes/disable_diagnostics.go b/internal/codefixes/disable_diagnostics.go deleted file mode 100644 index 91109639..00000000 --- a/internal/codefixes/disable_diagnostics.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package codefixes provides Effect-specific code fix providers. -package codefixes - -import ( - "github.com/effect-ts/tsgo/internal/rule" - "github.com/effect-ts/tsgo/internal/rules" -) - -// EffectDisableErrorCodes returns all Effect diagnostic codes that support disable actions. -func EffectDisableErrorCodes() []int32 { - return rule.AllCodes(rules.All) -} - -// CodeToRuleName returns the rule name for an Effect diagnostic code. -// Returns "unknown" if the code is not recognized. -func CodeToRuleName(code int32) string { - name := rule.CodeToRuleName(rules.All, code) - if name == "" { - return "unknown" - } - return name -} - -// DisableNextLineComment generates the comment text to disable a rule for the next line. -// Format: // @effect-diagnostics-next-line {ruleName}:off -func DisableNextLineComment(ruleName string) string { - return "// @effect-diagnostics-next-line " + ruleName + ":off\n" -} - -// DisableFileComment generates the comment text to disable a rule for the entire file. -// Format: /** @effect-diagnostics {ruleName}:skip-file */ -func DisableFileComment(ruleName string) string { - return "/** @effect-diagnostics " + ruleName + ":skip-file */\n" -} - -// DisableNextLineDescription generates the action description for "Disable for this line". -func DisableNextLineDescription(ruleName string) string { - return "Disable " + ruleName + " for this line" -} - -// DisableFileDescription generates the action description for "Disable for entire file". -func DisableFileDescription(ruleName string) string { - return "Disable " + ruleName + " for entire file" -} diff --git a/internal/codegen/codegen.go b/internal/codegen/codegen.go index 15a568b7..7bdd90ab 100644 --- a/internal/codegen/codegen.go +++ b/internal/codegen/codegen.go @@ -9,13 +9,3 @@ type Codegen struct { // Description explains what the codegen does. Description string } - -// ByName finds a codegen by name in a slice. Returns nil if not found. -func ByName(codegens []Codegen, name string) *Codegen { - for i := range codegens { - if codegens[i].Name == name { - return &codegens[i] - } - } - return nil -} diff --git a/internal/codegens/codegens.go b/internal/codegens/codegens.go index e362b5a0..fcdca723 100644 --- a/internal/codegens/codegens.go +++ b/internal/codegens/codegens.go @@ -20,8 +20,3 @@ var All = []codegen.Codegen{ Description: "Generate Schemas from types", }, } - -// ByName finds a codegen by name. Returns nil if not found. -func ByName(name string) *codegen.Codegen { - return codegen.ByName(All, name) -} diff --git a/internal/completions/completions.go b/internal/completions/completions.go index 23e58086..8fde3010 100644 --- a/internal/completions/completions.go +++ b/internal/completions/completions.go @@ -21,13 +21,3 @@ var All = []completion.Completion{ rpcMakeClasses, schemaBrand, } - -// ByName finds a completion by its unique name. -func ByName(name string) *completion.Completion { - for i := range All { - if All[i].Name == name { - return &All[i] - } - } - return nil -} diff --git a/internal/effectconfigraw/hooks.go b/internal/effectconfigraw/hooks.go index bda14b04..57105926 100644 --- a/internal/effectconfigraw/hooks.go +++ b/internal/effectconfigraw/hooks.go @@ -199,25 +199,6 @@ func findEffectPluginRaw(plugins []any) (*collections.OrderedMap[string, any], i return nil, -1 } -func cloneRawJSON(value any) any { - switch value := value.(type) { - case *collections.OrderedMap[string, any]: - cloned := new(collections.OrderedMap[string, any]) - for key, entryValue := range value.Entries() { - cloned.Set(key, cloneRawJSON(entryValue)) - } - return cloned - case []any: - cloned := make([]any, len(value)) - for i, entryValue := range value { - cloned[i] = cloneRawJSON(entryValue) - } - return cloned - default: - return value - } -} - func cloneEffectOptions(source *etscore.EffectPluginOptions) *etscore.EffectPluginOptions { if source == nil { return nil diff --git a/internal/rewriter/tracker.go b/internal/rewriter/tracker.go index 18f406e8..04da78f5 100644 --- a/internal/rewriter/tracker.go +++ b/internal/rewriter/tracker.go @@ -39,10 +39,6 @@ func NewTracker(raw *change.Tracker) *Tracker { return &Tracker{Tracker: raw} } -func (t *Tracker) Raw() *change.Tracker { - return t.Tracker -} - func (t *Tracker) GetChanges() map[string][]*lsproto.TextEdit { t.flushPendingBefore() return t.Tracker.GetChanges() @@ -69,39 +65,6 @@ func (t *Tracker) ReplaceNode(sourceFile *ast.SourceFile, oldNode *ast.Node, new t.ReplaceRangeWithText(sourceFile, rng, text) } -func (t *Tracker) ReplaceNodeWithNodes(sourceFile *ast.SourceFile, oldNode *ast.Node, newNodes []*ast.Node, options *change.NodeOptions) { - if len(newNodes) == 0 || oldNode == nil { - return - } - if len(newNodes) == 1 { - newNode := newNodes[0] - t.ReplaceNode(sourceFile, oldNode, newNode, options) - return - } - if options == nil { - options = &change.NodeOptions{ - LeadingTriviaOption: change.LeadingTriviaOptionExclude, - TrailingTriviaOption: change.TrailingTriviaOptionExclude, - } - } - parts := []string{t.consumePendingPrefix(sourceFile, oldNode)} - for _, node := range newNodes { - if node == nil { - continue - } - parts = append(parts, t.nodeText(sourceFile, node)) - } - text := strings.Join(parts, "\n") - if options.Prefix != "" { - text = options.Prefix + text - } - if options.Suffix != "" { - text += options.Suffix - } - rng := t.GetAdjustedRange(sourceFile, oldNode, oldNode, options.LeadingTriviaOption, options.TrailingTriviaOption) - t.ReplaceRangeWithText(sourceFile, rng, text) -} - func (t *Tracker) InsertNodeBefore(sourceFile *ast.SourceFile, before *ast.Node, newNode *ast.Node, blankLineBetween bool, leadingTriviaOption change.LeadingTriviaOption) { if before == nil || newNode == nil { return diff --git a/internal/schemagen/schemagen.go b/internal/schemagen/schemagen.go index b35b36b4..890e0938 100644 --- a/internal/schemagen/schemagen.go +++ b/internal/schemagen/schemagen.go @@ -8,9 +8,9 @@ import ( "errors" "fmt" + "github.com/effect-ts/tsgo/internal/rewriter" "github.com/effect-ts/tsgo/internal/typeparser" "github.com/microsoft/typescript-go/shim/ast" - "github.com/effect-ts/tsgo/internal/rewriter" "github.com/microsoft/typescript-go/shim/scanner" ) @@ -679,16 +679,6 @@ func (g *SchemaGen) createExportSchemaClassDeclaration(name string, properties [ ) } -// ProcessNode is the public entry point for processing a single type node. -// On error, returns a comment node describing the error. -func (g *SchemaGen) ProcessNode(node *ast.Node) *ast.Node { - result, err := g.processNode(node) - if err != nil { - return g.Tracker.NewIdentifier(fmt.Sprintf("undefined /* %s */", err.Error())) - } - return result -} - // Process converts an interface or type alias declaration into a schema statement. // On error, returns a comment node describing the error. func (g *SchemaGen) Process(node *ast.Node, preferClass bool) *ast.Node { diff --git a/internal/schemagen/structural.go b/internal/schemagen/structural.go index 7b947bc1..75a1c8c2 100644 --- a/internal/schemagen/structural.go +++ b/internal/schemagen/structural.go @@ -11,10 +11,10 @@ import ( "maps" "regexp" + "github.com/effect-ts/tsgo/internal/rewriter" "github.com/effect-ts/tsgo/internal/typeparser" "github.com/microsoft/typescript-go/shim/ast" "github.com/microsoft/typescript-go/shim/checker" - "github.com/effect-ts/tsgo/internal/rewriter" ) // StructuralSchemaGen holds the context for converting resolved types to Schema expressions. @@ -696,8 +696,3 @@ func (g *StructuralSchemaGen) Process(typeMap map[string]*checker.Type, scope *a return g.schemaStatements } - -// Statements returns the accumulated schema statements. -func (g *StructuralSchemaGen) Statements() []*ast.Node { - return g.schemaStatements -} diff --git a/internal/typeparser/schema_type.go b/internal/typeparser/schema_type.go index 58515073..17ec60ad 100644 --- a/internal/typeparser/schema_type.go +++ b/internal/typeparser/schema_type.go @@ -14,20 +14,6 @@ var effectSchemaParserModuleDescriptor = newPackageSourceFileDescriptor("effect" // SchemaTypeId is the property key for Schema's variance struct. const SchemaTypeId = "~effect/Schema/Schema" -// parseSchemaVarianceStruct checks if a type is a Schema variance struct (has _A, _I, _R). -func (tp *TypeParser) parseSchemaVarianceStruct(t *checker.Type) bool { - a := tp.extractInvariantType(t, "_A") - if a == nil { - return false - } - i := tp.extractInvariantType(t, "_I") - if i == nil { - return false - } - r := tp.extractCovariantType(t, "_R") - return r != nil -} - // IsSchemaType returns true if the type is a Schema type (v4 or v3). func (tp *TypeParser) IsSchemaType(t *checker.Type, atLocation *ast.Node) bool { if tp == nil { diff --git a/package.json b/package.json index d22b4971..8dadcbdf 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "check": "repoctl check", "check:repoctl": "pnpm --filter @effect/tsgo-repoctl check", "changeset": "changeset", - "lint": "CGO_ENABLED=0 golangci-lint run ./...", + "lint": "repoctl lint", "test": "repoctl test", "test:repoctl": "pnpm --filter @effect/tsgo-repoctl test", "build": "repoctl build local" From fc9500ef08b91b1697d610b1f875b6486324760e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:40:51 +0000 Subject: [PATCH 03/23] chore: update generated/latest --- .changeset/cache-generated-shims.md | 5 ----- .changeset/deadcode-lint.md | 5 ----- .changeset/non-interactive-setup.md | 5 ----- .changeset/readme-absolute-links.md | 5 ----- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 12 ++++++++++++ _packages/tsgo/package.json | 2 +- etscore/version_generated.go | 2 +- 21 files changed, 35 insertions(+), 29 deletions(-) delete mode 100644 .changeset/cache-generated-shims.md delete mode 100644 .changeset/deadcode-lint.md delete mode 100644 .changeset/non-interactive-setup.md delete mode 100644 .changeset/readme-absolute-links.md diff --git a/.changeset/cache-generated-shims.md b/.changeset/cache-generated-shims.md deleted file mode 100644 index e71b1f60..00000000 --- a/.changeset/cache-generated-shims.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Cache generated TypeScript-Go shims by their effective inputs during local repository setup and CI runs. diff --git a/.changeset/deadcode-lint.md b/.changeset/deadcode-lint.md deleted file mode 100644 index 13187233..00000000 --- a/.changeset/deadcode-lint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Remove unused internal helpers and add dead-code analysis to the repository lint workflow. diff --git a/.changeset/non-interactive-setup.md b/.changeset/non-interactive-setup.md deleted file mode 100644 index 9f44abb3..00000000 --- a/.changeset/non-interactive-setup.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": minor ---- - -Add a non-interactive `setup` workflow with explicit project, integration, diagnostic, editor, preview, and apply options. Setup now also recommends running the package manager install command whenever it changes `package.json`. diff --git a/.changeset/readme-absolute-links.md b/.changeset/readme-absolute-links.md deleted file mode 100644 index a1a35d7b..00000000 --- a/.changeset/readme-absolute-links.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Use absolute GitHub URLs for links in the published README so documentation links remain clickable on npm. diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index 57050fb6..302856fb 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index e265f9d6..901cd02e 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index 7cf90a27..89218373 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index 62662e98..6ecb8972 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index bea7cf56..c92cf5bc 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index c6cb4e72..3b6ea2d6 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index f3f659fe..564d0e0b 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index d018c2a8..11112e2c 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index 4c2d061c..5d8c7fc1 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index 9581b7c4..60af06d1 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index 14860086..4bce648f 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index 27767b2d..1de4d7f8 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 0275fde8..875824a7 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.34.0 + ## 0.33.0 ## 0.32.1 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index f80a20d3..3f926182 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.33.0", + "version": "0.34.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index d2a37649..9ad65194 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,17 @@ # @effect/tsgo +## 0.34.0 + +### Minor Changes + +- 974513e: Add a non-interactive `setup` workflow with explicit project, integration, diagnostic, editor, preview, and apply options. Setup now also recommends running the package manager install command whenever it changes `package.json`. + +### Patch Changes + +- ba4ec9a: Cache generated TypeScript-Go shims by their effective inputs during local repository setup and CI runs. +- 6d0ffda: Remove unused internal helpers and add dead-code analysis to the repository lint workflow. +- 892021a: Use absolute GitHub URLs for links in the published README so documentation links remain clickable on npm. + ## 0.33.0 ### Minor Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index bff941b3..9e48f338 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.33.0", + "version": "0.34.0", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/etscore/version_generated.go b/etscore/version_generated.go index a40765fb..f009bff3 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.33.0" +const EffectVersion = "0.34.0" From 9483d6129bb2e5baceebcf620a76416834f3f573 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:00:03 +0000 Subject: [PATCH 04/23] chore: update generated/latest --- .changeset/upstream-update-descriptions.md | 5 + .github/workflows/update-upstreams.yml | 8 +- _tools/repoctl/src/upstream.ts | 192 ++++++++++++++++++++- _tools/repoctl/test/upstream.test.ts | 73 +++++++- 4 files changed, 271 insertions(+), 7 deletions(-) create mode 100644 .changeset/upstream-update-descriptions.md diff --git a/.changeset/upstream-update-descriptions.md b/.changeset/upstream-update-descriptions.md new file mode 100644 index 00000000..3061789e --- /dev/null +++ b/.changeset/upstream-update-descriptions.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": minor +--- + +Generate detailed upstream update pull request descriptions with explicit version changes and the TypeScript-Go commits introduced by the update. diff --git a/.github/workflows/update-upstreams.yml b/.github/workflows/update-upstreams.yml index 5990f029..2b813bee 100644 --- a/.github/workflows/update-upstreams.yml +++ b/.github/workflows/update-upstreams.yml @@ -33,6 +33,8 @@ jobs: - name: Update upstream metadata id: upstream + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: pnpm exec repoctl upstream update - name: Open metadata update pull request @@ -40,12 +42,13 @@ jobs: if: steps.upstream.outputs.has_changes == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_BODY: ${{ steps.upstream.outputs.description }} run: | pnpm exec repoctl github open-pr-if-changed \ --base main \ --head update-upstreams \ --title 'chore: update upstreams' \ - --body 'Automated update of TypeScript-Go upstream component tags.' \ + --body "$PR_BODY" \ --commit-message 'chore: update upstream metadata' \ --check generation_check_id='Generate TypeScript next tag' @@ -85,12 +88,13 @@ jobs: if: steps.upstream.outputs.has_changes == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_BODY: ${{ steps.upstream.outputs.description }} run: | pnpm exec repoctl github open-pr-if-changed \ --base main \ --head update-upstreams \ --title 'chore: update upstreams' \ - --body 'Automated update of upstream metadata, generated TypeScript next-tag sources, and Nix inputs.' \ + --body "$PR_BODY" \ --commit-message 'chore: generate TypeScript next tag' \ --check validation_check_id='Validate TypeScript next tag' diff --git a/_tools/repoctl/src/upstream.ts b/_tools/repoctl/src/upstream.ts index b91815ca..466ffd57 100644 --- a/_tools/repoctl/src/upstream.ts +++ b/_tools/repoctl/src/upstream.ts @@ -174,6 +174,11 @@ interface TypeScriptMetadata { readonly gitHead: string } +interface TypeScriptGoCommit { + readonly sha: string + readonly message: string +} + interface OxlintSelection { readonly oxlintVersion: string readonly tsgolintVersion: string @@ -309,9 +314,15 @@ export const formatOxlintConfigurationSchema = (value: unknown): string | undefi return `${JSON.stringify(value, null, 2)}\n` } -const fetchJson = Effect.fnUntraced(function*(url: string) { +const fetchJson = Effect.fnUntraced(function*(url: string, authenticate = false) { + const token = authenticate ? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN : undefined const response = yield* Effect.tryPromise({ - try: () => fetch(url, { headers: { "User-Agent": "effect-tsgo-repoctl" } }), + try: () => fetch(url, { + headers: { + "User-Agent": "effect-tsgo-repoctl", + ...(token === undefined ? {} : { Authorization: `Bearer ${token}` }) + } + }), catch: (error) => new UpstreamManifestError({ reason: `Unable to fetch ${url}: ${String(error)}` }) }) if (!response.ok) { @@ -323,6 +334,169 @@ const fetchJson = Effect.fnUntraced(function*(url: string) { }) }) +const GitHubComparison = Schema.Struct({ + total_commits: Schema.Number, + commits: Schema.Array(Schema.Struct({ + sha: GitRevision, + commit: Schema.Struct({ message: NonEmptyString }) + })) +}) + +const fetchTypeScriptGoCommits = Effect.fnUntraced(function*(before: string, after: string) { + if (before === after) { + return [] + } + const commits: Array = [] + let page = 1 + let totalCommits = 0 + do { + const url = [ + `https://api.github.com/repos/microsoft/typescript-go/compare/${before}...${after}`, + `?per_page=100&page=${page}` + ].join("") + const comparison = yield* fetchJson(url, true) + const decoded = yield* Schema.decodeUnknownEffect(GitHubComparison)(comparison).pipe( + Effect.mapError((error) => new UpstreamManifestError({ + reason: `Unable to read TypeScript-Go comparison: ${error.message}` + })) + ) + totalCommits = decoded.total_commits + if (decoded.commits.length === 0 && commits.length < totalCommits) { + return yield* new UpstreamManifestError({ reason: "TypeScript-Go comparison ended before all commits were read" }) + } + commits.push(...decoded.commits.map(({ commit, sha }) => ({ + sha, + message: commit.message.split(/[\r\n]/, 1)[0]! + }))) + page++ + } while (commits.length < totalCommits) + return commits +}) + +export interface UpstreamUpdateDescriptionOptions { + readonly before: typeof Upstream.Type + readonly after: typeof Upstream.Type + readonly nextCommits: ReadonlyArray + readonly schemaChanged: boolean + readonly oxlintSchemaChanged: boolean +} + +export const formatUpstreamUpdateDescription = ({ + after, + before, + nextCommits, + oxlintSchemaChanged, + schemaChanged +}: UpstreamUpdateDescriptionOptions): string => { + const beforeVitePlus = before.profiles.find(({ name }) => name === "vite-plus")! + const afterVitePlus = after.profiles.find(({ name }) => name === "vite-plus")! + const beforeVitePlusVersion = /^Vite\+ (.+) compatibility runtime$/.exec(beforeVitePlus.description)?.[1] + const afterVitePlusVersion = /^Vite\+ (.+) compatibility runtime$/.exec(afterVitePlus.description)?.[1] + const versionUpdates = [ + { + label: "TypeScript next", + packageName: "typescript", + spec: "typescript@next", + previous: before.tags.typescript.next, + updated: after.tags.typescript.next + }, + { + label: "TypeScript latest", + packageName: "typescript", + spec: "typescript@latest", + previous: before.tags.typescript.latest, + updated: after.tags.typescript.latest + }, + { + label: "Oxlint", + packageName: "oxlint", + spec: "oxlint@latest", + previous: before.tags.oxlint.latest, + updated: after.tags.oxlint.latest + }, + { + label: "Oxlint TypeScript-Go lint plugin", + packageName: "oxlint-tsgolint", + spec: "oxlint-tsgolint@latest", + previous: before.tags["oxlint-tsgolint"].latest, + updated: after.tags["oxlint-tsgolint"].latest + }, + { + label: "Vite+", + packageName: "vite-plus", + spec: "vite-plus@latest", + previous: beforeVitePlusVersion, + updated: afterVitePlusVersion + }, + { + label: "Vite+ Oxlint runtime", + packageName: "oxlint", + spec: "oxlint", + previous: beforeVitePlus.dependencies.oxlint, + updated: afterVitePlus.dependencies.oxlint + }, + { + label: "Vite+ TypeScript-Go lint runtime", + packageName: "oxlint-tsgolint", + spec: "oxlint-tsgolint", + previous: beforeVitePlus.dependencies["oxlint-tsgolint"], + updated: afterVitePlus.dependencies["oxlint-tsgolint"] + } + ].filter((update): update is typeof update & { readonly previous: string; readonly updated: string } => + update.previous !== undefined && update.updated !== undefined && update.previous !== update.updated) + const previousNext = before.components.typescript[before.tags.typescript.next]!.gitHead + const updatedNext = after.components.typescript[after.tags.typescript.next]!.gitHead + const sections = ["Automated update of upstream metadata, generated TypeScript next-tag sources, and Nix inputs."] + + if (versionUpdates.length > 0) { + sections.push([ + "## Version updates", + "", + ...versionUpdates.map(({ label, packageName, previous, spec, updated }) => + `- ${label}: [\`${spec}\`](https://www.npmjs.com/package/${packageName}/v/${updated}) \`${previous}\` -> \`${updated}\``) + ].join("\n")) + } + if (previousNext !== updatedNext) { + sections.push([ + "## TypeScript-Go", + "", + `- Previous commit: [\`${previousNext}\`](https://github.com/microsoft/typescript-go/commit/${previousNext})`, + `- Updated commit: [\`${updatedNext}\`](https://github.com/microsoft/typescript-go/commit/${updatedNext})`, + `- Compare: https://github.com/microsoft/typescript-go/compare/${previousNext}...${updatedNext}` + ].join("\n")) + } + if (nextCommits.length > 0) { + sections.push([ + "## Upstream commits", + "", + ...nextCommits.map(({ message, sha }) => + `- [${sha.slice(0, 7)}](https://github.com/microsoft/typescript-go/commit/${sha}) ${message}`) + ].join("\n")) + } + const otherUpdates = [ + ...(schemaChanged ? ["- Refreshed the tsconfig schema from JSON Schema Store."] : []), + ...(oxlintSchemaChanged ? ["- Refreshed the Oxlint configuration schema from the selected package."] : []) + ] + if (otherUpdates.length > 0) { + sections.push(["## Other updates", "", ...otherUpdates].join("\n")) + } + return sections.join("\n\n") +} + +export const formatGitHubOutputs = (outputs: Readonly>) => + Object.entries(outputs).map(([name, value]) => { + const normalizedValue = value.replace(/\r\n?/g, "\n") + if (!normalizedValue.includes("\n")) { + return `${name}=${normalizedValue}\n` + } + let delimiter = `repoctl_${name}` + const lines = new Set(normalizedValue.split("\n")) + while (lines.has(delimiter)) { + delimiter += "_" + } + return `${name}<<${delimiter}\n${normalizedValue}\n${delimiter}\n` + }).join("") + const resolveRemoteTag = Effect.fnUntraced(function*(repositoryRoot: string, repository: string, tag: string) { const output = yield* runCommandString("git", repositoryRoot, [ "ls-remote", @@ -538,6 +712,15 @@ export const updateUpstream = Effect.fnUntraced(function*(repositoryRoot: string const oxlintSchemaChanged = oxlintSchema !== currentOxlintSchema const hasChanges = metadataChanged || schemaChanged || oxlintSchemaChanged + const nextCommits = yield* fetchTypeScriptGoCommits(nextBefore.gitHead, next.gitHead) + const description = formatUpstreamUpdateDescription({ + before: upstream, + after: updated, + nextCommits, + schemaChanged, + oxlintSchemaChanged + }) + if (metadataChanged) { yield* fs.writeFileString( path.join(repositoryRoot, "_packages", "tsgo", "upstream.json"), @@ -568,12 +751,13 @@ export const updateUpstream = Effect.fnUntraced(function*(repositoryRoot: string latest_previous_version: latestBefore.npmVersion, latest_previous_git_head: latestBefore.gitHead, latest_version: latest.npmVersion, - latest_git_head: latest.gitHead + latest_git_head: latest.gitHead, + description } if (process.env.GITHUB_OUTPUT !== undefined) { yield* Effect.tryPromise(() => appendFile( process.env.GITHUB_OUTPUT!, - Object.entries(outputs).map(([name, value]) => `${name}=${value}\n`).join("") + formatGitHubOutputs(outputs) )) } yield* Effect.log(hasChanges ? "Updated upstream metadata" : "Upstream metadata is current") diff --git a/_tools/repoctl/test/upstream.test.ts b/_tools/repoctl/test/upstream.test.ts index 5ab51753..bf65111a 100644 --- a/_tools/repoctl/test/upstream.test.ts +++ b/_tools/repoctl/test/upstream.test.ts @@ -6,8 +6,10 @@ import { decodeLatestNpmVersion, decodeUpstream, findTypeScriptVersion, + formatGitHubOutputs, formatOxlintConfigurationSchema, formatTSConfigSchema, + formatUpstreamUpdateDescription, getComponent } from "../src/upstream.ts" import { resolveUpstreamInfo } from "../src/upstreamResolve.ts" @@ -17,7 +19,7 @@ const secondRevision = "1123456789abcdef0123456789abcdef01234567" const thirdRevision = "2123456789abcdef0123456789abcdef01234567" const manifest = () => ({ - schemaVersion: 4, + schemaVersion: 4 as const, tags: { typescript: { latest: "7.0.0", @@ -208,6 +210,75 @@ test("finds a TypeScript npm version by its git head", () => { }, secondRevision), "7.0.2") }) +test("describes upstream version and TypeScript-Go commit updates", () => { + const before = manifest() + const after = manifest() + const afterTypeScript = after.components.typescript as Record + const afterOxlint = after.components.oxlint as Record + after.tags.typescript.next = "7.2.0" + afterTypeScript["7.2.0"] = { gitHead: thirdRevision } + after.tags.oxlint.latest = "1.2.0" + afterOxlint["1.2.0"] = { gitHead: thirdRevision } + after.profiles[0]!.description = "Vite+ 1.1.0 compatibility runtime" + after.profiles[0]!.dependencies.oxlint = "1.2.0" + + assert.equal(formatUpstreamUpdateDescription({ + before, + after, + nextCommits: [{ sha: thirdRevision, message: "Add a useful feature" }], + schemaChanged: true, + oxlintSchemaChanged: false + }), [ + "Automated update of upstream metadata, generated TypeScript next-tag sources, and Nix inputs.", + "", + "## Version updates", + "", + "- TypeScript next: [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.2.0) `7.1.0` -> `7.2.0`", + "- Oxlint: [`oxlint@latest`](https://www.npmjs.com/package/oxlint/v/1.2.0) `1.1.0` -> `1.2.0`", + "- Vite+: [`vite-plus@latest`](https://www.npmjs.com/package/vite-plus/v/1.1.0) `1.0.0` -> `1.1.0`", + "- Vite+ Oxlint runtime: [`oxlint`](https://www.npmjs.com/package/oxlint/v/1.2.0) `1.0.0` -> `1.2.0`", + "", + "## TypeScript-Go", + "", + `- Previous commit: [\`${secondRevision}\`](https://github.com/microsoft/typescript-go/commit/${secondRevision})`, + `- Updated commit: [\`${thirdRevision}\`](https://github.com/microsoft/typescript-go/commit/${thirdRevision})`, + `- Compare: https://github.com/microsoft/typescript-go/compare/${secondRevision}...${thirdRevision}`, + "", + "## Upstream commits", + "", + `- [${thirdRevision.slice(0, 7)}](https://github.com/microsoft/typescript-go/commit/${thirdRevision}) Add a useful feature`, + "", + "## Other updates", + "", + "- Refreshed the tsconfig schema from JSON Schema Store." + ].join("\n")) +}) + +test("writes multiline descriptions as GitHub step outputs", () => { + assert.equal(formatGitHubOutputs({ has_changes: "true", description: "first\nsecond" }), [ + "has_changes=true", + "description< { assert.equal(formatTSConfigSchema({ definitions: {}, title: "tsconfig" }), [ "{", From 30c71e353947b1bf38c31cb3a6b258d605d0b9c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:08:26 +0000 Subject: [PATCH 05/23] chore: update generated/latest --- _packages/tsgo/upstream.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_packages/tsgo/upstream.json b/_packages/tsgo/upstream.json index 7a55e29f..bb4e2810 100644 --- a/_packages/tsgo/upstream.json +++ b/_packages/tsgo/upstream.json @@ -3,7 +3,7 @@ "tags": { "typescript": { "latest": "7.0.2", - "next": "7.1.0-dev.20260805.1" + "next": "7.1.0-dev.20260806.1" }, "oxlint": { "latest": "1.77.0" @@ -17,8 +17,8 @@ "7.0.2": { "gitHead": "2bd066d87f5bafd315be9f40889d0a60b9e58e0b" }, - "7.1.0-dev.20260805.1": { - "gitHead": "12318e599d21f516defea3b20e5d44b9369da723" + "7.1.0-dev.20260806.1": { + "gitHead": "86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee" } }, "oxlint-tsgolint": { From 3e588098d721b3b4548b7228cecab68b8ae07004 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:14:09 +0000 Subject: [PATCH 06/23] chore: update generated/latest --- .changeset/update-upstreams.md | 5 +++++ flake.lock | 8 ++++---- flake.nix | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/update-upstreams.md diff --git a/.changeset/update-upstreams.md b/.changeset/update-upstreams.md new file mode 100644 index 00000000..27c46980 --- /dev/null +++ b/.changeset/update-upstreams.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260806.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee) commit `86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2). diff --git a/flake.lock b/flake.lock index 9e2928b4..bf1a6c50 100644 --- a/flake.lock +++ b/flake.lock @@ -43,17 +43,17 @@ "typescript-go-src": { "flake": false, "locked": { - "lastModified": 1785807459, - "narHash": "sha256-aW/VXTodfPOfAszo3WQ+yyX2H3uPw9Jd6x24hhSKOe0=", + "lastModified": 1785946683, + "narHash": "sha256-g8BHkacd00IkM9+b9bUhQrn3CnfhwnnVmouEkce1+pE=", "owner": "microsoft", "repo": "typescript-go", - "rev": "12318e599d21f516defea3b20e5d44b9369da723", + "rev": "86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee", "type": "github" }, "original": { "owner": "microsoft", "repo": "typescript-go", - "rev": "12318e599d21f516defea3b20e5d44b9369da723", + "rev": "86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee", "type": "github" } }, diff --git a/flake.nix b/flake.nix index 340d4907..b3712ae5 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,7 @@ nixpkgsUnstable.url = "github:NixOS/nixpkgs/nixos-unstable"; /* Source of truth: the next profile in `_packages/tsgo/upstream.json`. */ typescript-go-src = { - url = "github:microsoft/typescript-go/12318e599d21f516defea3b20e5d44b9369da723?submodules=1"; + url = "github:microsoft/typescript-go/86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee?submodules=1"; flake = false; }; /* Derived from the selected TypeScript-Go revision and recorded in the manifest. */ From 78c3531e40c3e7d0e0a9825caa48c8642d95dad0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:21:39 +0000 Subject: [PATCH 07/23] chore: update generated/latest --- .changeset/fix-tagged-template-ts2731.md | 5 +++ .../effect_in_failure_ts2589_test.go | 37 +++++++++++++++++++ internal/typeparser/get_type_at_location.go | 8 ++++ 3 files changed, 50 insertions(+) create mode 100644 .changeset/fix-tagged-template-ts2731.md diff --git a/.changeset/fix-tagged-template-ts2731.md b/.changeset/fix-tagged-template-ts2731.md new file mode 100644 index 00000000..c6162373 --- /dev/null +++ b/.changeset/fix-tagged-template-ts2731.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Avoid false positive TS2731 diagnostics for symbol-valued interpolations in tagged template literals when Effect diagnostic rules traverse the template expression. diff --git a/internal/effecttest/effect_in_failure_ts2589_test.go b/internal/effecttest/effect_in_failure_ts2589_test.go index 3b360998..550c081d 100644 --- a/internal/effecttest/effect_in_failure_ts2589_test.go +++ b/internal/effecttest/effect_in_failure_ts2589_test.go @@ -40,6 +40,43 @@ func TestEffectInFailureCanTriggerPluginOnlyTS2589(t *testing.T) { } } +func TestTaggedTemplateSymbolInterpolationDoesNotReportTS2731(t *testing.T) { + t.Parallel() + + for _, rule := range []string{"anyUnknownInErrorContext", "effectInFailure"} { + t.Run(rule, func(t *testing.T) { + diagnostics := collectDiagnosticStringsFromContent(t, buildTaggedTemplateSymbolCase(rule)) + if hasDiagnosticCode(diagnostics, "TS2731:") { + t.Fatalf("did not expect TS2731 with %s enabled, got %v", rule, diagnostics) + } + }) + } +} + +func buildTaggedTemplateSymbolCase(rule string) string { + return `// @filename: tsconfig.json +{ + "compilerOptions": { + "plugins": [ + { + "name": "@effect/language-service", + "diagnosticSeverity": { + "` + rule + `": "error" + } + } + ] + } +} + +// @filename: test.ts +const self: unique symbol = Symbol("self") +function sql(strings: TemplateStringsArray, ...args: Array): string { + return strings.join("?") + args.length +} +export const q = sql` + "`(${self}) < 1`" + ` +` +} + func TestIssue362RemedaChunkDoesNotReportExcessiveTypeDepth(t *testing.T) { t.Parallel() diff --git a/internal/typeparser/get_type_at_location.go b/internal/typeparser/get_type_at_location.go index 87c03577..65ae32ab 100644 --- a/internal/typeparser/get_type_at_location.go +++ b/internal/typeparser/get_type_at_location.go @@ -49,6 +49,14 @@ func (tp TypeParser) getTypeAtLocationUncached(node *ast.Node) (result *checker. return nil } + // Tagged templates pass interpolation values directly to the tag function; + // they do not stringify them. Asking the checker for the type of the inner + // TemplateExpression forces a normally unreachable checking path that can + // emit TS2731 for symbol-typed interpolations as a side effect. + if node.Kind == ast.KindTemplateExpression && node.Parent != nil && ast.IsTaggedTemplateExpression(node.Parent) { + return nil + } + // A meta property used as a call callee (import.defer(...)) has no type of // its own and the checker debug-asserts when asked (checkMetaProperty); the // enclosing call expression carries the meaningful type. From b10575f05cf799577196544af42f36ee41020863 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:31:30 +0000 Subject: [PATCH 08/23] chore: update generated/latest --- .../fix-typed-schema-decoder-call-input.md | 5 + .../effect_in_failure_ts2589_test.go | 2 + internal/rules/prefer_typed_schema_decoder.go | 6 +- .../preferTypedSchemaDecoder.errors.txt | 19 +- ...der.flows.preferTypedSchemaDecoder.mermaid | 27 +- .../preferTypedSchemaDecoder.pipings.txt | 52 +++- .../preferTypedSchemaDecoder.quickfixes.txt | 276 ++++++++++++++++++ .../effect-v4/preferTypedSchemaDecoder.ts | 11 + 8 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 .changeset/fix-typed-schema-decoder-call-input.md diff --git a/.changeset/fix-typed-schema-decoder-call-input.md b/.changeset/fix-typed-schema-decoder-call-input.md new file mode 100644 index 00000000..100628ea --- /dev/null +++ b/.changeset/fix-typed-schema-decoder-call-input.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Prevent `preferTypedSchemaDecoder` from panicking when a decoder input is produced by a preceding call or pipe transformation. diff --git a/internal/effecttest/effect_in_failure_ts2589_test.go b/internal/effecttest/effect_in_failure_ts2589_test.go index 550c081d..56949ce9 100644 --- a/internal/effecttest/effect_in_failure_ts2589_test.go +++ b/internal/effecttest/effect_in_failure_ts2589_test.go @@ -45,6 +45,8 @@ func TestTaggedTemplateSymbolInterpolationDoesNotReportTS2731(t *testing.T) { for _, rule := range []string{"anyUnknownInErrorContext", "effectInFailure"} { t.Run(rule, func(t *testing.T) { + t.Parallel() + diagnostics := collectDiagnosticStringsFromContent(t, buildTaggedTemplateSymbolCase(rule)) if hasDiagnosticCode(diagnostics, "TS2731:") { t.Fatalf("did not expect TS2731 with %s enabled, got %v", rule, diagnostics) diff --git a/internal/rules/prefer_typed_schema_decoder.go b/internal/rules/prefer_typed_schema_decoder.go index 697a666a..a755f305 100644 --- a/internal/rules/prefer_typed_schema_decoder.go +++ b/internal/rules/prefer_typed_schema_decoder.go @@ -146,8 +146,10 @@ func analyzeTypedSchemaDecoderApplication(tp *typeparser.TypeParser, c *checker. } assignableType := inputType - if literal := ast.SkipParentheses(inputNode); literal != nil && (literal.Kind == ast.KindObjectLiteralExpression || literal.Kind == ast.KindArrayLiteralExpression) { - assignableType = checker.Checker_checkExpressionWithContextualType(c, literal, schemaType.E, nil, checker.CheckModeTypeOnly) + if inputNode != nil { + if literal := ast.SkipParentheses(inputNode); literal != nil && (literal.Kind == ast.KindObjectLiteralExpression || literal.Kind == ast.KindArrayLiteralExpression) { + assignableType = checker.Checker_checkExpressionWithContextualType(c, literal, schemaType.E, nil, checker.CheckModeTypeOnly) + } } if assignableType == nil || !checker.Checker_isTypeAssignableTo(c, assignableType, schemaType.E) { return nil diff --git a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.errors.txt b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.errors.txt index 6c84e0a6..815761a5 100644 --- a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.errors.txt +++ b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.errors.txt @@ -12,9 +12,11 @@ Effect version: 4.0.0 /.src/preferTypedSchemaDecoder.ts(29,42): warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeSync` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownSync`. effect(preferTypedSchemaDecoder) /.src/preferTypedSchemaDecoder.ts(30,22): warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeSync` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownSync`. effect(preferTypedSchemaDecoder) /.src/preferTypedSchemaDecoder.ts(33,35): warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeSync` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownSync`. effect(preferTypedSchemaDecoder) +/.src/preferTypedSchemaDecoder.ts(65,31): warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeEffect` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownEffect`. effect(preferTypedSchemaDecoder) +/.src/preferTypedSchemaDecoder.ts(67,67): warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeEffect` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownEffect`. effect(preferTypedSchemaDecoder) -==== /.src/preferTypedSchemaDecoder.ts (11 errors) ==== +==== /.src/preferTypedSchemaDecoder.ts (13 errors) ==== // @effect-diagnostics *:off // @effect-diagnostics preferTypedSchemaDecoder:warning @@ -94,3 +96,18 @@ Effect version: 4.0.0 decodeGeneric(person) decodeNestedGeneric(person) + // Regression test for https://github.com/Effect-TS/tsgo/issues/572. + const NamedPerson = Schema.Struct({ name: Schema.String }) + + function makeFields(name: string): { readonly name: string } { + return { name } + } + + export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + ~~~~~~~~~~~~~~~~~~~ +!!! warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeEffect` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownEffect`. effect(preferTypedSchemaDecoder) + + export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + ~~~~~~~~~~~~~~~~~~~ +!!! warning TS377112: This input is already assignable to the schema's Encoded type. Use `decodeEffect` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownEffect`. effect(preferTypedSchemaDecoder) + diff --git a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.flows.preferTypedSchemaDecoder.mermaid b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.flows.preferTypedSchemaDecoder.mermaid index 17108cf2..8dbaca04 100644 --- a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.flows.preferTypedSchemaDecoder.mermaid +++ b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.flows.preferTypedSchemaDecoder.mermaid @@ -36,6 +36,20 @@ flowchart TB 34[/"type: #123; name: string; coordinates: #91;number, number#93;; #125;
node: person"/] 35["type: #123; readonly name: string; readonly coordinates: readonly #91;number, number#93;; #125;
callee: decodeNestedGeneric
args: #91;#93;"] 36[/"type: #lt;T extends string#gt;#40;input: #123; readonly name: T; readonly coordinates: #91;number, number#93;; #125;#41; =#gt; #123; readonly name: string; readonly coordinates: readonly #91;number, number#93;; #125;
node: decodeNestedGeneric"/] + 37[/"type: #123; name: String; #125;
node: #123; name: Schema.String #125;"/] + 38["type: Struct#lt;#123; readonly name: String; #125;#gt;
callee: Schema.Struct
args: #91;#93;"] + 39[/"type: #lt;const Fields extends Struct.Fields#gt;#40;fields: Fields#41; =#gt; Struct#lt;Fields#gt;
node: Schema.Struct"/] + 40[["type: #40;name: string#41; =#gt; #123; readonly name: string; #125;
node: function makeFields#40;name: string#41;: #123; readonly name: string #125; #123;#92;n return #123; name #125;#92;n#125;"]] + 41[/"type: #123; name: string; #125;
node: #123; name #125;"/] + 42[/"type: Effect#lt;#123; readonly name: string; #125;, SchemaError, never#gt;
node: Schema.decodeUnknownEffect#40;NamedPerson#41;#40;makeFields#40;#quot;Ada#quot;#41;#41;"/] + 43[/"type: #quot;Ada#quot;
node: #quot;Ada#quot;"/] + 44["type: #123; readonly name: string; #125;
callee: makeFields
args: #91;#93;"] + 45[/"type: #40;name: string#41; =#gt; #123; readonly name: string; #125;
node: makeFields"/] + 46[/"type: #quot;Ada#quot;
node: #quot;Ada#quot;"/] + 47["type: #123; readonly name: string; #125;
callee: makeFields
args: #91;#93;"] + 48["type: Effect#lt;#123; readonly name: string; #125;, SchemaError, never#gt;
callee: Schema.decodeUnknownEffect
args: #91;NamedPerson#93;"] + 49[/"type: #lt;S extends Constraint#gt;#40;schema: S, options?: ParseOptions #124; undefined#41; =#gt; #40;input: unknown, options?: ParseOptions #124; undefined#41; =#gt; Effect#lt;S#91;#quot;Type#quot;#93;, SchemaError, S#91;#quot;DecodingServices#quot;#93;#gt;
node: Schema.decodeUnknownEffect"/] + 50[/"type: Struct#lt;#123; readonly name: String; #125;#gt;
node: NamedPerson"/] 1 -->|"kind: pipe"| 2 3 -->|"kind: transformCallee"| 2 2 -->|"kind: usedBy"| 0 @@ -51,4 +65,15 @@ flowchart TB 31 -->|"kind: pipe"| 32 33 -->|"kind: transformCallee"| 32 34 -->|"kind: pipe"| 35 - 36 -->|"kind: transformCallee"| 35 \ No newline at end of file + 36 -->|"kind: transformCallee"| 35 + 37 -->|"kind: pipe"| 38 + 39 -->|"kind: transformCallee"| 38 + 41 -->|"kind: potentialReturn"| 40 + 41 -->|"kind: usedBy"| 40 + 43 -->|"kind: pipe"| 44 + 45 -->|"kind: transformCallee"| 44 + 44 -->|"kind: usedBy"| 42 + 46 -->|"kind: pipe"| 47 + 49 -->|"kind: transformCallee"| 48 + 50 -->|"kind: transformArg"| 48 + 47 -->|"kind: pipe"| 48 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.pipings.txt b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.pipings.txt index 367eb605..c7f96f7a 100644 --- a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.pipings.txt +++ b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.pipings.txt @@ -1,4 +1,4 @@ -==== /.src/preferTypedSchemaDecoder.ts (23 flows) ==== +==== /.src/preferTypedSchemaDecoder.ts (26 flows) ==== === Piping Flow === Location: 8:15 - 11:3 @@ -321,3 +321,53 @@ Transformations (1): callee: decodeNestedGeneric args: (constant) outType: { readonly name: string; readonly coordinates: readonly [number, number]; } + +=== Piping Flow === +Location: 59:20 - 59:59 +Node: Schema.Struct({ name: Schema.String }) +Node Kind: KindCallExpression + +Subject: { name: Schema.String } +Subject Type: { name: String; } + +Transformations (1): + [0] kind: call + callee: Schema.Struct + args: (constant) + outType: Struct<{ readonly name: String; }> + +=== Piping Flow === +Location: 65:23 - 65:82 +Node: Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) +Node Kind: KindCallExpression + +Subject: "Ada" +Subject Type: "Ada" + +Transformations (2): + [0] kind: call + callee: makeFields + args: (constant) + outType: { readonly name: string; } + [1] kind: call + callee: Schema.decodeUnknownEffect(NamedPerson) + args: (constant) + outType: Effect<{ readonly name: string; }, SchemaError, never> + +=== Piping Flow === +Location: 67:35 - 67:100 +Node: pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) +Node Kind: KindCallExpression + +Subject: "Ada" +Subject Type: "Ada" + +Transformations (2): + [0] kind: pipe + callee: makeFields + args: (constant) + outType: { readonly name: string; } + [1] kind: pipe + callee: Schema.decodeUnknownEffect + args: [NamedPerson] + outType: Effect<{ readonly name: string; }, SchemaError, never> diff --git a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.quickfixes.txt b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.quickfixes.txt index 10c6ba50..8f015cdd 100644 --- a/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.quickfixes.txt +++ b/testdata/baselines/reference/effect-v4/preferTypedSchemaDecoder.quickfixes.txt @@ -54,6 +54,16 @@ Fix 1: "Disable preferTypedSchemaDecoder for entire file" Fix 2: "Replace with decodeSync" +[D12] (65:31-65:50) TS377112: This input is already assignable to the schema's Encoded type. Use `decodeEffect` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownEffect`. effect(preferTypedSchemaDecoder) + Fix 0: "Disable preferTypedSchemaDecoder for this line" + Fix 1: "Disable preferTypedSchemaDecoder for entire file" + Fix 2: "Replace with decodeEffect" + +[D13] (67:67-67:86) TS377112: This input is already assignable to the schema's Encoded type. Use `decodeEffect` to preserve compile-time type checking instead of discarding the input type through `decodeUnknownEffect`. effect(preferTypedSchemaDecoder) + Fix 0: "Disable preferTypedSchemaDecoder for this line" + Fix 1: "Disable preferTypedSchemaDecoder for entire file" + Fix 2: "Replace with decodeEffect" + === Quick Fix Application Results === === [D1] Fix 0: "Disable preferTypedSchemaDecoder for this line" === @@ -122,6 +132,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D2] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -189,6 +210,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D3] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -256,6 +288,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D4] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -323,6 +366,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D5] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -390,6 +444,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D6] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -457,6 +522,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D7] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -524,6 +600,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D8] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -591,6 +678,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D9] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -658,6 +756,17 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + === [D10] Fix 0: "Disable preferTypedSchemaDecoder for this line" === skipped by default @@ -731,3 +840,170 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + + +=== [D12] Fix 0: "Disable preferTypedSchemaDecoder for this line" === +skipped by default + +=== [D12] Fix 1: "Disable preferTypedSchemaDecoder for entire file" === +skipped by default + +=== [D12] Fix 2: "Replace with decodeEffect" === + +--- file:///.src/preferTypedSchemaDecoder.ts --- +// @effect-diagnostics *:off +// @effect-diagnostics preferTypedSchemaDecoder:warning + +import { pipe, Schema } from "effect" +import { decodeUnknownSync } from "effect/Schema" +import * as SchemaParser from "effect/SchemaParser" + +const Person = Schema.Struct({ + name: Schema.String, + coordinates: Schema.Tuple([Schema.Number, Schema.Number]) +}) + +const person = { + name: "Ada", + coordinates: [1, 2] as [number, number] +} + +// Typed variables and contextually typed literals are reported. +export const sync = Schema.decodeUnknownSync(Person)(person) +export const literal = Schema.decodeUnknownSync(Person)({ name: "Ada", coordinates: [1, 2] }) +export const effect = Schema.decodeUnknownEffect(Person)(person) +export const exit = Schema.decodeUnknownExit(Person)(person) +export const option = Schema.decodeUnknownOption(Person)(person) +export const result = Schema.decodeUnknownResult(Person)(person) +export const promise = Schema.decodeUnknownPromise(Person)(person) + +// SchemaParser APIs and piping forms are normalized through piping flows. +export const parser = SchemaParser.decodeUnknownSync(Person)(person) +export const piped = pipe(person, Schema.decodeUnknownSync(Person)) +export const named = decodeUnknownSync(Person)(person) + +// Application options do not prevent recognizing the decoder application. +export const withOptions = Schema.decodeUnknownSync(Person)({ name: "Ada", coordinates: [1, 2] }, { errors: "all" }) + +declare const unknownInput: unknown +declare const anyInput: any +declare const wrongInput: { readonly name: number } + +// Unknown, any, incompatible, and unresolved generic inputs are valid uses. +Schema.decodeUnknownSync(Person)(unknownInput) +Schema.decodeUnknownSync(Person)(anyInput) +Schema.decodeUnknownSync(Person)(wrongInput) +Schema.decodeUnknownSync(Person)({ name: 1, coordinates: [1, 2] }) +Schema.decodeUnknownSync(Person)({ name: "Ada" }) +Schema.decodeUnknownSync(Person)({ name: "Ada", coordinates: [1] }) + +function decodeGeneric(input: T) { + return Schema.decodeUnknownSync(Person)(input) +} + +function decodeNestedGeneric(input: { readonly name: T; readonly coordinates: [number, number] }) { + return Schema.decodeUnknownSync(Person)(input) +} + +decodeGeneric(person) +decodeNestedGeneric(person) + +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) + + +=== [D13] Fix 0: "Disable preferTypedSchemaDecoder for this line" === +skipped by default + +=== [D13] Fix 1: "Disable preferTypedSchemaDecoder for entire file" === +skipped by default + +=== [D13] Fix 2: "Replace with decodeEffect" === + +--- file:///.src/preferTypedSchemaDecoder.ts --- +// @effect-diagnostics *:off +// @effect-diagnostics preferTypedSchemaDecoder:warning + +import { pipe, Schema } from "effect" +import { decodeUnknownSync } from "effect/Schema" +import * as SchemaParser from "effect/SchemaParser" + +const Person = Schema.Struct({ + name: Schema.String, + coordinates: Schema.Tuple([Schema.Number, Schema.Number]) +}) + +const person = { + name: "Ada", + coordinates: [1, 2] as [number, number] +} + +// Typed variables and contextually typed literals are reported. +export const sync = Schema.decodeUnknownSync(Person)(person) +export const literal = Schema.decodeUnknownSync(Person)({ name: "Ada", coordinates: [1, 2] }) +export const effect = Schema.decodeUnknownEffect(Person)(person) +export const exit = Schema.decodeUnknownExit(Person)(person) +export const option = Schema.decodeUnknownOption(Person)(person) +export const result = Schema.decodeUnknownResult(Person)(person) +export const promise = Schema.decodeUnknownPromise(Person)(person) + +// SchemaParser APIs and piping forms are normalized through piping flows. +export const parser = SchemaParser.decodeUnknownSync(Person)(person) +export const piped = pipe(person, Schema.decodeUnknownSync(Person)) +export const named = decodeUnknownSync(Person)(person) + +// Application options do not prevent recognizing the decoder application. +export const withOptions = Schema.decodeUnknownSync(Person)({ name: "Ada", coordinates: [1, 2] }, { errors: "all" }) + +declare const unknownInput: unknown +declare const anyInput: any +declare const wrongInput: { readonly name: number } + +// Unknown, any, incompatible, and unresolved generic inputs are valid uses. +Schema.decodeUnknownSync(Person)(unknownInput) +Schema.decodeUnknownSync(Person)(anyInput) +Schema.decodeUnknownSync(Person)(wrongInput) +Schema.decodeUnknownSync(Person)({ name: 1, coordinates: [1, 2] }) +Schema.decodeUnknownSync(Person)({ name: "Ada" }) +Schema.decodeUnknownSync(Person)({ name: "Ada", coordinates: [1] }) + +function decodeGeneric(input: T) { + return Schema.decodeUnknownSync(Person)(input) +} + +function decodeNestedGeneric(input: { readonly name: T; readonly coordinates: [number, number] }) { + return Schema.decodeUnknownSync(Person)(input) +} + +decodeGeneric(person) +decodeNestedGeneric(person) + +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeEffect(NamedPerson)) + diff --git a/testdata/tests/effect-v4/preferTypedSchemaDecoder.ts b/testdata/tests/effect-v4/preferTypedSchemaDecoder.ts index b5205ca0..a15fe55c 100644 --- a/testdata/tests/effect-v4/preferTypedSchemaDecoder.ts +++ b/testdata/tests/effect-v4/preferTypedSchemaDecoder.ts @@ -54,3 +54,14 @@ function decodeNestedGeneric(input: { readonly name: T; readon decodeGeneric(person) decodeNestedGeneric(person) + +// Regression test for https://github.com/Effect-TS/tsgo/issues/572. +const NamedPerson = Schema.Struct({ name: Schema.String }) + +function makeFields(name: string): { readonly name: string } { + return { name } +} + +export const decoded = Schema.decodeUnknownEffect(NamedPerson)(makeFields("Ada")) + +export const pipedCallExpression = pipe("Ada", makeFields, Schema.decodeUnknownEffect(NamedPerson)) From 172e5836d549e48440fb54bf77b73d6ee42021a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:05:07 +0000 Subject: [PATCH 09/23] chore: update generated/latest --- .changeset/fix-tagged-template-ts2731.md | 5 -- .../fix-typed-schema-decoder-call-input.md | 5 -- .changeset/update-upstreams.md | 5 -- .changeset/upstream-update-descriptions.md | 5 -- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 + _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 + _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 + _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 + _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 + _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 + _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 + _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 13 +++++ _packages/tsgo/package.json | 2 +- .../028-incremental-effect-options.patch | 47 +++++++++++++++++++ etscore/version_generated.go | 2 +- 22 files changed, 83 insertions(+), 29 deletions(-) delete mode 100644 .changeset/fix-tagged-template-ts2731.md delete mode 100644 .changeset/fix-typed-schema-decoder-call-input.md delete mode 100644 .changeset/update-upstreams.md delete mode 100644 .changeset/upstream-update-descriptions.md create mode 100644 _patches/typescript-go/028-incremental-effect-options.patch diff --git a/.changeset/fix-tagged-template-ts2731.md b/.changeset/fix-tagged-template-ts2731.md deleted file mode 100644 index c6162373..00000000 --- a/.changeset/fix-tagged-template-ts2731.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Avoid false positive TS2731 diagnostics for symbol-valued interpolations in tagged template literals when Effect diagnostic rules traverse the template expression. diff --git a/.changeset/fix-typed-schema-decoder-call-input.md b/.changeset/fix-typed-schema-decoder-call-input.md deleted file mode 100644 index 100628ea..00000000 --- a/.changeset/fix-typed-schema-decoder-call-input.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Prevent `preferTypedSchemaDecoder` from panicking when a decoder input is produced by a preceding call or pipe transformation. diff --git a/.changeset/update-upstreams.md b/.changeset/update-upstreams.md deleted file mode 100644 index 27c46980..00000000 --- a/.changeset/update-upstreams.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260806.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee) commit `86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2). diff --git a/.changeset/upstream-update-descriptions.md b/.changeset/upstream-update-descriptions.md deleted file mode 100644 index 3061789e..00000000 --- a/.changeset/upstream-update-descriptions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": minor ---- - -Generate detailed upstream update pull request descriptions with explicit version changes and the TypeScript-Go commits introduced by the update. diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index 302856fb..47fcbb9f 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index 901cd02e..6dedd5ac 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index 89218373..453a051a 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index 6ecb8972..db1d4ef1 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index c92cf5bc..fdb7446f 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index 3b6ea2d6..1f4cfb33 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index 564d0e0b..e65faaca 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index 11112e2c..c55e6c96 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index 5d8c7fc1..759848e5 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index 60af06d1..2d821822 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index 4bce648f..09c625e6 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index 1de4d7f8..4997c551 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 875824a7..7d825a77 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.35.0 + ## 0.34.0 ## 0.33.0 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index 3f926182..a044800b 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.34.0", + "version": "0.35.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index 9ad65194..a7c492c4 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,18 @@ # @effect/tsgo +## 0.35.0 + +### Minor Changes + +- f25821b: Generate detailed upstream update pull request descriptions with explicit version changes and the TypeScript-Go commits introduced by the update. + +### Patch Changes + +- 3fe06c8: Avoid false positive TS2731 diagnostics for symbol-valued interpolations in tagged template literals when Effect diagnostic rules traverse the template expression. +- 989964b: Prevent `preferTypedSchemaDecoder` from panicking when a decoder input is produced by a preceding call or pipe transformation. +- 86c30b1: Persist Effect plugin options in TypeScript build information so incremental builds recheck semantic diagnostics when extended tsconfig plugin settings change. +- b8e7314: Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260806.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee) commit `86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2). + ## 0.34.0 ### Minor Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 9e48f338..864dc67a 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.34.0", + "version": "0.35.0", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/_patches/typescript-go/028-incremental-effect-options.patch b/_patches/typescript-go/028-incremental-effect-options.patch new file mode 100644 index 00000000..3f280758 --- /dev/null +++ b/_patches/typescript-go/028-incremental-effect-options.patch @@ -0,0 +1,47 @@ +diff --git a/internal/execute/incremental/buildInfo.go b/internal/execute/incremental/buildInfo.go +index 61ae063d4..47da02331 100644 +--- a/internal/execute/incremental/buildInfo.go ++++ b/internal/execute/incremental/buildInfo.go +@@ -4,6 +4,7 @@ import ( + "fmt" + "iter" + ++ "github.com/effect-ts/tsgo/etscore" + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/collections" + "github.com/microsoft/typescript-go/internal/core" +@@ -476,6 +477,7 @@ type BuildInfo struct { + FileInfos []*BuildInfoFileInfo `json:"fileInfos,omitzero"` + FileIdsList [][]BuildInfoFileId `json:"fileIdsList,omitzero"` + Options *collections.OrderedMap[string, any] `json:"options,omitzero"` ++ Effect *etscore.EffectPluginOptions `json:"effect,omitzero"` + ReferencedMap []*BuildInfoReferenceMapEntry `json:"referencedMap,omitzero"` + SemanticDiagnosticsPerFile []*BuildInfoSemanticDiagnostic `json:"semanticDiagnosticsPerFile,omitzero"` + EmitDiagnosticsPerFile []*BuildInfoDiagnosticsOfFile `json:"emitDiagnosticsPerFile,omitzero"` +@@ -529,1 +531,2 @@ ++ options.Effect = b.Effect + return options + +diff --git a/internal/execute/incremental/snapshottobuildinfo.go b/internal/execute/incremental/snapshottobuildinfo.go +index 909fab087..6595cd86c 100644 +--- a/internal/execute/incremental/snapshottobuildinfo.go ++++ b/internal/execute/incremental/snapshottobuildinfo.go +@@ -19,3 +19,4 @@ func snapshotToBuildInfo(snapshot *snapshot, program *compiler.Program, buildInfo + buildInfo := &BuildInfo{ + Version: core.Version(), ++ Effect: snapshot.options.Effect, + } +diff --git a/internal/tsoptions/declscompiler.go b/internal/tsoptions/declscompiler.go +index c6a70ff9e..fd1672ea7 100644 +--- a/internal/tsoptions/declscompiler.go ++++ b/internal/tsoptions/declscompiler.go +@@ -1243,6 +1243,9 @@ func CompilerOptionsAffectSemanticDiagnostics( + oldOptions *core.CompilerOptions, + newOptions *core.CompilerOptions, + ) bool { ++ if !reflect.DeepEqual(oldOptions.Effect, newOptions.Effect) { ++ return true ++ } + return optionsHaveChanges(oldOptions, newOptions, func(option *CommandLineOption) bool { + return option.AffectsSemanticDiagnostics + }) diff --git a/etscore/version_generated.go b/etscore/version_generated.go index f009bff3..10923604 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.34.0" +const EffectVersion = "0.35.0" From f38ad4d0d163c8e6c60fc1dc01b3bce8a25c7b8f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:19:48 +0000 Subject: [PATCH 10/23] chore: update generated/latest --- .changeset/curly-lions-lint.md | 5 + .github/workflows/validate-oxlint.yml | 3 + _packages/tsgo/.gitignore | 1 + _packages/tsgo/package.json | 8 + _packages/tsgo/tsdown.config.ts | 35 +++++ docs/README.md | 53 ++++++- etscore/oxlint_schema_test.go | 141 ++++++++++++++++++ oxlint-presets/antipattern.json | 30 ++++ oxlint-presets/correctness.json | 28 ++++ oxlint-presets/effect-native.json | 32 ++++ oxlint-presets/recommended.json | 91 +++++++++++ oxlint-presets/style.json | 45 ++++++ oxlint-schema.json | 3 + schema.json | 5 + .../.oxlintrc-recommended-override.json | 6 + .../tests/oxlint/.oxlintrc-recommended.json | 3 + testdata/tests/oxlint/global-date.ts | 1 + testdata/tests/oxlint/smoke.mjs | 21 +++ 18 files changed, 506 insertions(+), 5 deletions(-) create mode 100644 .changeset/curly-lions-lint.md create mode 100644 oxlint-presets/antipattern.json create mode 100644 oxlint-presets/correctness.json create mode 100644 oxlint-presets/effect-native.json create mode 100644 oxlint-presets/recommended.json create mode 100644 oxlint-presets/style.json create mode 100644 testdata/tests/oxlint/.oxlintrc-recommended-override.json create mode 100644 testdata/tests/oxlint/.oxlintrc-recommended.json create mode 100644 testdata/tests/oxlint/global-date.ts diff --git a/.changeset/curly-lions-lint.md b/.changeset/curly-lions-lint.md new file mode 100644 index 00000000..29e13ff9 --- /dev/null +++ b/.changeset/curly-lions-lint.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": minor +--- + +Add recommended and category-specific shared configurations for Oxlint. diff --git a/.github/workflows/validate-oxlint.yml b/.github/workflows/validate-oxlint.yml index 808089e3..679694c5 100644 --- a/.github/workflows/validate-oxlint.yml +++ b/.github/workflows/validate-oxlint.yml @@ -68,6 +68,9 @@ jobs: --version "${{ matrix.oxlint.version }}" --target linux-x64 + - name: Build @effect/tsgo package + run: pnpm --filter @effect/tsgo build + - name: Smoke test Oxlint runtime env: TSGOLINT_PATH: ${{ steps.tsgolint.outputs.artifact_path }} diff --git a/_packages/tsgo/.gitignore b/_packages/tsgo/.gitignore index f2276e5f..5baa34a7 100644 --- a/_packages/tsgo/.gitignore +++ b/_packages/tsgo/.gitignore @@ -2,4 +2,5 @@ dist/ node_modules/ README.md oxlint-schema.json +oxlint-presets/ schema.json diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 864dc67a..91c41b40 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -31,12 +31,20 @@ "./lib/getExePath": { "types": "./lib/getExePath.d.ts", "default": "./lib/getExePath.js" + }, + "./oxlint-presets": { + "types": "./oxlint-presets/index.d.ts", + "default": "./oxlint-presets/index.js" + }, + "./oxlint-presets/*.json": { + "default": "./oxlint-presets/*.json" } }, "files": [ "dist/", "lib/", "README.md", + "oxlint-presets/", "oxlint-schema.json", "schema.json" ], diff --git a/_packages/tsgo/tsdown.config.ts b/_packages/tsgo/tsdown.config.ts index 15a4c973..0011161e 100644 --- a/_packages/tsgo/tsdown.config.ts +++ b/_packages/tsgo/tsdown.config.ts @@ -19,6 +19,41 @@ const copyPackageFiles = () => { const oxlintSchemaJson = yield* fs.readFileString("../../oxlint-schema.json") yield* fs.writeFileString(path.join("oxlint-schema.json"), oxlintSchemaJson) + + const presetDirectory = path.join("oxlint-presets") + yield* fs.remove(presetDirectory, { recursive: true, force: true }) + yield* fs.makeDirectory(presetDirectory) + + const presetSources: Array = [] + const presetSourceDirectory = path.join("../../oxlint-presets") + const presetFiles = (yield* fs.readDirectory(presetSourceDirectory)) + .filter((name) => name.endsWith(".json")) + .sort() + const exportNames: Array = [] + for (const presetFile of presetFiles) { + const fileName = presetFile.slice(0, -".json".length) + const exportName = fileName.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase()) + const source = yield* fs.readFileString(path.join(presetSourceDirectory, presetFile)) + yield* fs.writeFileString(path.join(presetDirectory, presetFile), source) + presetSources.push(`export const ${exportName} = ${source.trim()}\n`) + exportNames.push(exportName) + } + + const generatedHeader = "// Code generated by the @effect/tsgo package build. DO NOT EDIT.\n\n" + yield* fs.writeFileString( + path.join(presetDirectory, "index.js"), + `${generatedHeader}${presetSources.join("\n")}\nexport const presets = {\n${ + exportNames.map((name) => ` ${name},`).join("\n") + }\n}\n` + ) + yield* fs.writeFileString( + path.join(presetDirectory, "index.d.ts"), + `${generatedHeader}import type { OxlintConfig } from "oxlint"\n\n${ + exportNames.map((name) => `export declare const ${name}: OxlintConfig`).join("\n") + }\n\nexport declare const presets: Readonly<{\n${ + exportNames.map((name) => ` ${name}: OxlintConfig`).join("\n") + }\n}>\n` + ) }).pipe(Effect.provide(Layer.merge(NodeFileSystem.layer, NodePath.layerPosix))) return Effect.runPromise(program) diff --git a/docs/README.md b/docs/README.md index 88ceb9f6..cc7cc401 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,24 +18,67 @@ Update the scripts section of your `package.json` to include the following: ``` This will patch Oxlint to use the Effect TypeScript-Go integration after any package installation. To avoid patching TypeScript, you can use the `--no-typescript` flag: `effect-tsgo patch --no-typescript --oxlint`. This will patch Oxlint to use the Effect TypeScript-Go integration without patching TypeScript. +## LSP diagnostics + +When you have the Effect LSP enabled as well, we recommend setting `diagnostics` to `false` in the LSP plugin settings so that Effect diagnostics are reported only by Oxlint and do not appear twice: + +```jsonc +{ + "compilerOptions": { + "plugins": [ + { + "name": "@effect/language-service", + "diagnostics": false + } + ] + } +} +``` + Run pnpm install to install the dependencies and run the prepare script to patch Oxlint. ``` pnpm install ``` -Effect rules require Oxlint's type-aware mode and the `effecttsgo` plugin. We recommend enabling both in `.oxlintrc.json` and using the schema shipped with `@effect/tsgo` for validation and completions: +Effect rules require Oxlint's type-aware mode and the `effecttsgo` plugin. The recommended preset enables both and configures the recommended Effect rules. Use the schema shipped with `@effect/tsgo` for validation and completions: ```json { "$schema": "./node_modules/@effect/tsgo/oxlint-schema.json", - "options": { - "typeAware": true - }, - "plugins": ["effecttsgo"] + "extends": [ + "./node_modules/@effect/tsgo/oxlint-presets/recommended.json" + ] } ``` +The package also provides presets for each diagnostic category: `correctness`, `antipattern`, `effect-native`, and `style`. Extended configurations are applied in order, and rules in the project configuration take precedence, so categories can be combined and individual rules can be adjusted: + +```json +{ + "$schema": "./node_modules/@effect/tsgo/oxlint-schema.json", + "extends": [ + "./node_modules/@effect/tsgo/oxlint-presets/correctness.json", + "./node_modules/@effect/tsgo/oxlint-presets/effect-native.json" + ], + "rules": { + "effecttsgo/global-date": "error", + "effecttsgo/global-console": "off" + } +} +``` + +With `oxlint.config.ts`, import the same configurations from the package: + +```ts +import { recommended } from "@effect/tsgo/oxlint-presets" +import { defineConfig } from "oxlint" + +export default defineConfig({ + extends: [recommended] +}) +``` + You can then run Oxlint normally: ```sh diff --git a/etscore/oxlint_schema_test.go b/etscore/oxlint_schema_test.go index 106be87f..44461746 100644 --- a/etscore/oxlint_schema_test.go +++ b/etscore/oxlint_schema_test.go @@ -6,9 +6,11 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "testing" + "github.com/effect-ts/tsgo/etscore" "github.com/effect-ts/tsgo/internal/rules" "github.com/microsoft/typescript-go/shim/testutil/baseline" ) @@ -45,6 +47,145 @@ func TestGenerateOxlintSchemaMatchesReference(t *testing.T) { t.Fatalf("oxlint-schema.json is out of date:\n%s", strings.Join(diffLines, "\n")) } +func TestGenerateOxlintPresetsMatchReferences(t *testing.T) { + root := repoRoot(t) + presets, err := generateOxlintPresets() + if err != nil { + t.Fatalf("generateOxlintPresets() error = %v", err) + } + + referenceDirectory := filepath.Join(root, "oxlint-presets") + localDirectory := filepath.Join(root, "testdata", "baselines", "local", "oxlint-presets") + update := os.Getenv("UPDATE_OXLINT_PRESETS") == "1" + + for name, actual := range presets { + referencePath := filepath.Join(referenceDirectory, name) + if update { + writeIfChanged(t, referencePath, actual) + continue + } + + expected, err := os.ReadFile(referencePath) + if err != nil { + t.Fatalf("failed to read reference preset %q: %v", referencePath, err) + } + if bytes.Equal(actual, expected) { + continue + } + + localPath := filepath.Join(localDirectory, name) + writeIfChanged(t, localPath, actual) + diff := baseline.DiffText(referencePath, localPath, string(expected), string(actual)) + t.Fatalf("%s is out of date:\n%s", referencePath, diff) + } + + entries, err := os.ReadDir(referenceDirectory) + if err != nil && !os.IsNotExist(err) { + t.Fatalf("failed to read preset directory %q: %v", referenceDirectory, err) + } + for _, entry := range entries { + if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { + continue + } + if _, ok := presets[entry.Name()]; ok { + continue + } + path := filepath.Join(referenceDirectory, entry.Name()) + if update { + if err := os.Remove(path); err != nil { + t.Fatalf("failed to remove stale preset %q: %v", path, err) + } + continue + } + t.Fatalf("stale Oxlint preset %q", path) + } +} + +type oxlintPreset struct { + Options oxlintPresetOptions `json:"options"` + Plugins []string `json:"plugins"` + Rules map[string]string `json:"rules"` +} + +type oxlintPresetOptions struct { + TypeAware bool `json:"typeAware"` +} + +func generateOxlintPresets() (map[string][]byte, error) { + groups := rules.MetadataGroups() + generated := make(map[string][]byte, len(groups)+1) + + recommendedSeverities := make(map[string]string) + for _, current := range rules.All { + if severity := oxlintSeverity(current.DefaultSeverity); severity != "off" { + recommendedSeverities[oxlintRuleName(current.Name)] = severity + } + } + for _, preset := range rules.MetadataPresets() { + for name, severity := range preset.DiagnosticSeverity { + oxlintName := oxlintRuleName(name) + if current, next := recommendedSeverities[oxlintName], oxlintSeverity(severity); current != "error" && next != "off" { + recommendedSeverities[oxlintName] = next + } + } + } + if err := addOxlintPreset(generated, "recommended.json", recommendedSeverities); err != nil { + return nil, err + } + + for _, group := range groups { + severities := make(map[string]string) + for _, current := range rules.All { + if current.Group == group.ID { + severities[oxlintRuleName(current.Name)] = "warn" + } + } + if err := addOxlintPreset(generated, oxlintRuleName(group.ID)+".json", severities); err != nil { + return nil, err + } + } + + return generated, nil +} + +func addOxlintPreset(generated map[string][]byte, name string, severities map[string]string) error { + if _, exists := generated[name]; exists { + return fmt.Errorf("duplicate Oxlint preset %q", name) + } + ruleNames := make([]string, 0, len(severities)) + for name := range severities { + ruleNames = append(ruleNames, name) + } + slices.Sort(ruleNames) + configuredRules := make(map[string]string, len(ruleNames)) + for _, name := range ruleNames { + configuredRules["effecttsgo/"+name] = severities[name] + } + content, err := json.MarshalIndent(oxlintPreset{ + Options: oxlintPresetOptions{TypeAware: true}, + Plugins: []string{"effecttsgo"}, + Rules: configuredRules, + }, "", " ") + if err != nil { + return err + } + generated[name] = append(content, '\n') + return nil +} + +func oxlintSeverity(severity etscore.Severity) string { + switch severity { + case etscore.SeverityOff, etscore.SeveritySkipFile: + return "off" + case etscore.SeverityError: + return "error" + case etscore.SeverityWarning, etscore.SeveritySuggestion, etscore.SeverityMessage: + return "warn" + default: + panic(fmt.Sprintf("unsupported Effect severity %q", severity.String())) + } +} + func generateOxlintSchema() ([]byte, error) { root := repoRootForGeneration() baseSchemaContent, err := os.ReadFile(filepath.Join(root, "_tools", "oxlint-configuration-base-schema.json")) diff --git a/oxlint-presets/antipattern.json b/oxlint-presets/antipattern.json new file mode 100644 index 00000000..ed17be90 --- /dev/null +++ b/oxlint-presets/antipattern.json @@ -0,0 +1,30 @@ +{ + "options": { + "typeAware": true + }, + "plugins": [ + "effecttsgo" + ], + "rules": { + "effecttsgo/catch-unfailable-effect": "warn", + "effecttsgo/effect-fn-iife": "warn", + "effecttsgo/effect-gen-uses-adapter": "warn", + "effecttsgo/effect-in-failure": "warn", + "effecttsgo/effect-in-void-success": "warn", + "effecttsgo/global-error-in-effect-catch": "warn", + "effecttsgo/global-error-in-effect-failure": "warn", + "effecttsgo/layer-merge-all-with-dependencies": "warn", + "effecttsgo/lazy-effect": "warn", + "effecttsgo/lazy-promise-in-effect-sync": "warn", + "effecttsgo/leaking-requirements": "warn", + "effecttsgo/multiple-effect-provide": "warn", + "effecttsgo/prefer-unsafe-constructor": "warn", + "effecttsgo/return-effect-in-gen": "warn", + "effecttsgo/run-effect-inside-effect": "warn", + "effecttsgo/schema-sync-in-effect": "warn", + "effecttsgo/scope-in-layer-effect": "warn", + "effecttsgo/strict-effect-provide": "warn", + "effecttsgo/try-catch-in-effect-gen": "warn", + "effecttsgo/unknown-in-effect-catch": "warn" + } +} diff --git a/oxlint-presets/correctness.json b/oxlint-presets/correctness.json new file mode 100644 index 00000000..28a971a1 --- /dev/null +++ b/oxlint-presets/correctness.json @@ -0,0 +1,28 @@ +{ + "options": { + "typeAware": true + }, + "plugins": [ + "effecttsgo" + ], + "rules": { + "effecttsgo/any-unknown-in-error-context": "warn", + "effecttsgo/class-self-mismatch": "warn", + "effecttsgo/duplicate-package": "warn", + "effecttsgo/effect-fn-implicit-any": "warn", + "effecttsgo/floating-effect": "warn", + "effecttsgo/floating-effect-in-vitest": "warn", + "effecttsgo/generic-effect-services": "warn", + "effecttsgo/missing-effect-context": "warn", + "effecttsgo/missing-effect-error": "warn", + "effecttsgo/missing-layer-context": "warn", + "effecttsgo/missing-return-yield-star": "warn", + "effecttsgo/missing-star-in-yield-effect-gen": "warn", + "effecttsgo/non-object-effect-service-type": "warn", + "effecttsgo/outdated-api": "warn", + "effecttsgo/overridden-schema-constructor": "warn", + "effecttsgo/promise-in-effect-success": "warn", + "effecttsgo/schema-literal-non-finite": "warn", + "effecttsgo/schema-opaque-instance-member": "warn" + } +} diff --git a/oxlint-presets/effect-native.json b/oxlint-presets/effect-native.json new file mode 100644 index 00000000..dae70970 --- /dev/null +++ b/oxlint-presets/effect-native.json @@ -0,0 +1,32 @@ +{ + "options": { + "typeAware": true + }, + "plugins": [ + "effecttsgo" + ], + "rules": { + "effecttsgo/abort-controller-in-effect": "warn", + "effecttsgo/async-function": "warn", + "effecttsgo/crypto-random-uuid": "warn", + "effecttsgo/crypto-random-uuid-in-effect": "warn", + "effecttsgo/extends-native-error": "warn", + "effecttsgo/global-console": "warn", + "effecttsgo/global-console-in-effect": "warn", + "effecttsgo/global-date": "warn", + "effecttsgo/global-date-in-effect": "warn", + "effecttsgo/global-fetch": "warn", + "effecttsgo/global-fetch-in-effect": "warn", + "effecttsgo/global-random": "warn", + "effecttsgo/global-random-in-effect": "warn", + "effecttsgo/global-timers": "warn", + "effecttsgo/global-timers-in-effect": "warn", + "effecttsgo/instance-of-schema": "warn", + "effecttsgo/new-promise": "warn", + "effecttsgo/node-builtin-import": "warn", + "effecttsgo/prefer-schema-over-json": "warn", + "effecttsgo/process-env": "warn", + "effecttsgo/process-env-in-effect": "warn", + "effecttsgo/unsafe-effect-type-assertion": "warn" + } +} diff --git a/oxlint-presets/recommended.json b/oxlint-presets/recommended.json new file mode 100644 index 00000000..b8de604f --- /dev/null +++ b/oxlint-presets/recommended.json @@ -0,0 +1,91 @@ +{ + "options": { + "typeAware": true + }, + "plugins": [ + "effecttsgo" + ], + "rules": { + "effecttsgo/abort-controller-in-effect": "warn", + "effecttsgo/async-function": "warn", + "effecttsgo/catch-all-to-map-error": "warn", + "effecttsgo/catch-chain-to-first-success-of": "warn", + "effecttsgo/catch-tag-to-catch-reason": "warn", + "effecttsgo/catch-to-ignore": "warn", + "effecttsgo/catch-to-or-else-succeed": "warn", + "effecttsgo/catch-unfailable-effect": "warn", + "effecttsgo/class-self-mismatch": "error", + "effecttsgo/crypto-random-uuid": "warn", + "effecttsgo/crypto-random-uuid-in-effect": "warn", + "effecttsgo/duplicate-package": "warn", + "effecttsgo/effect-fn-iife": "warn", + "effecttsgo/effect-fn-implicit-any": "error", + "effecttsgo/effect-fn-opportunity": "warn", + "effecttsgo/effect-gen-uses-adapter": "warn", + "effecttsgo/effect-in-failure": "warn", + "effecttsgo/effect-in-void-success": "warn", + "effecttsgo/effect-map-flatten": "warn", + "effecttsgo/effect-map-void": "warn", + "effecttsgo/effect-succeed-with-void": "warn", + "effecttsgo/extends-native-error": "warn", + "effecttsgo/flat-map-to-map": "warn", + "effecttsgo/floating-effect": "error", + "effecttsgo/floating-effect-in-vitest": "error", + "effecttsgo/generic-effect-services": "warn", + "effecttsgo/global-console": "warn", + "effecttsgo/global-console-in-effect": "warn", + "effecttsgo/global-date": "warn", + "effecttsgo/global-date-in-effect": "warn", + "effecttsgo/global-error-in-effect-catch": "warn", + "effecttsgo/global-error-in-effect-failure": "warn", + "effecttsgo/global-fetch": "warn", + "effecttsgo/global-fetch-in-effect": "warn", + "effecttsgo/global-random": "warn", + "effecttsgo/global-random-in-effect": "warn", + "effecttsgo/global-timers": "warn", + "effecttsgo/global-timers-in-effect": "warn", + "effecttsgo/instance-of-schema": "warn", + "effecttsgo/layer-merge-all-with-dependencies": "warn", + "effecttsgo/lazy-effect": "warn", + "effecttsgo/lazy-promise-in-effect-sync": "warn", + "effecttsgo/leaking-requirements": "warn", + "effecttsgo/missing-effect-context": "error", + "effecttsgo/missing-effect-error": "error", + "effecttsgo/missing-layer-context": "error", + "effecttsgo/missing-return-yield-star": "error", + "effecttsgo/missing-star-in-yield-effect-gen": "error", + "effecttsgo/multiple-catch-tag": "warn", + "effecttsgo/multiple-effect-provide": "warn", + "effecttsgo/new-promise": "warn", + "effecttsgo/node-builtin-import": "warn", + "effecttsgo/non-object-effect-service-type": "error", + "effecttsgo/outdated-api": "warn", + "effecttsgo/overridden-schema-constructor": "error", + "effecttsgo/prefer-schema-over-json": "warn", + "effecttsgo/prefer-typed-schema-decoder": "warn", + "effecttsgo/prefer-unsafe-constructor": "warn", + "effecttsgo/process-env": "warn", + "effecttsgo/process-env-in-effect": "warn", + "effecttsgo/promise-in-effect-success": "warn", + "effecttsgo/redundant-map-error": "warn", + "effecttsgo/redundant-or-die": "warn", + "effecttsgo/redundant-schema-tag-identifier": "warn", + "effecttsgo/return-effect-in-gen": "warn", + "effecttsgo/run-effect-inside-effect": "warn", + "effecttsgo/schema-literal-non-finite": "error", + "effecttsgo/schema-number": "warn", + "effecttsgo/schema-opaque-instance-member": "error", + "effecttsgo/schema-struct-with-tag": "warn", + "effecttsgo/schema-sync-in-effect": "warn", + "effecttsgo/scope-in-layer-effect": "warn", + "effecttsgo/sync-to-succeed": "warn", + "effecttsgo/try-catch-in-effect-gen": "warn", + "effecttsgo/unknown-in-effect-catch": "warn", + "effecttsgo/unnecessary-effect-gen": "warn", + "effecttsgo/unnecessary-fail-yieldable-error": "warn", + "effecttsgo/unnecessary-pipe": "warn", + "effecttsgo/unnecessary-pipe-chain": "warn", + "effecttsgo/unnecessary-typeof-type": "warn", + "effecttsgo/unsafe-effect-type-assertion": "warn" + } +} diff --git a/oxlint-presets/style.json b/oxlint-presets/style.json new file mode 100644 index 00000000..ff2b3f5f --- /dev/null +++ b/oxlint-presets/style.json @@ -0,0 +1,45 @@ +{ + "options": { + "typeAware": true + }, + "plugins": [ + "effecttsgo" + ], + "rules": { + "effecttsgo/catch-all-to-map-error": "warn", + "effecttsgo/catch-chain-to-first-success-of": "warn", + "effecttsgo/catch-tag-to-catch-reason": "warn", + "effecttsgo/catch-to-ignore": "warn", + "effecttsgo/catch-to-or-else-succeed": "warn", + "effecttsgo/deterministic-keys": "warn", + "effecttsgo/effect-do-notation": "warn", + "effecttsgo/effect-fn-opportunity": "warn", + "effecttsgo/effect-map-flatten": "warn", + "effecttsgo/effect-map-void": "warn", + "effecttsgo/effect-succeed-with-void": "warn", + "effecttsgo/flat-map-to-map": "warn", + "effecttsgo/missed-pipeable-opportunity": "warn", + "effecttsgo/missing-effect-service-dependency": "warn", + "effecttsgo/missing-pipeable-signature": "warn", + "effecttsgo/multiple-catch-tag": "warn", + "effecttsgo/nested-effect-gen-yield": "warn", + "effecttsgo/new-schema-class": "warn", + "effecttsgo/prefer-schema-type-property": "warn", + "effecttsgo/prefer-typed-schema-decoder": "warn", + "effecttsgo/redundant-map-error": "warn", + "effecttsgo/redundant-or-die": "warn", + "effecttsgo/redundant-schema-tag-identifier": "warn", + "effecttsgo/schema-number": "warn", + "effecttsgo/schema-struct-with-tag": "warn", + "effecttsgo/schema-union-of-literals": "warn", + "effecttsgo/service-not-as-class": "warn", + "effecttsgo/strict-boolean-expressions": "warn", + "effecttsgo/sync-to-succeed": "warn", + "effecttsgo/unnecessary-arrow-block": "warn", + "effecttsgo/unnecessary-effect-gen": "warn", + "effecttsgo/unnecessary-fail-yieldable-error": "warn", + "effecttsgo/unnecessary-pipe": "warn", + "effecttsgo/unnecessary-pipe-chain": "warn", + "effecttsgo/unnecessary-typeof-type": "warn" + } +} diff --git a/oxlint-schema.json b/oxlint-schema.json index 5549d644..a995cda2 100644 --- a/oxlint-schema.json +++ b/oxlint-schema.json @@ -2410,6 +2410,9 @@ "effecttsgo/prefer-schema-type-property": { "$ref": "#/definitions/RuleNoConfig" }, + "effecttsgo/prefer-typed-schema-decoder": { + "$ref": "#/definitions/RuleNoConfig" + }, "effecttsgo/prefer-unsafe-constructor": { "$ref": "#/definitions/RuleNoConfig" }, diff --git a/schema.json b/schema.json index 7c509441..29b9e9c1 100644 --- a/schema.json +++ b/schema.json @@ -2135,6 +2135,11 @@ "default": "off", "description": "Disallows Schema.Schema.Type\u003ctypeof X\u003e in favor of typeof X.Type" }, + "preferTypedSchemaDecoder": { + "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", + "default": "suggestion", + "description": "Suggests typed Schema decoders when the input is assignable to the schema's Encoded type" + }, "preferUnsafeConstructor": { "$ref": "#/definitions/effectLanguageServicePluginSeverityDefinition", "default": "suggestion", diff --git a/testdata/tests/oxlint/.oxlintrc-recommended-override.json b/testdata/tests/oxlint/.oxlintrc-recommended-override.json new file mode 100644 index 00000000..b80c8e13 --- /dev/null +++ b/testdata/tests/oxlint/.oxlintrc-recommended-override.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../../oxlint-presets/recommended.json"], + "rules": { + "effecttsgo/global-date": "off" + } +} diff --git a/testdata/tests/oxlint/.oxlintrc-recommended.json b/testdata/tests/oxlint/.oxlintrc-recommended.json new file mode 100644 index 00000000..7621794a --- /dev/null +++ b/testdata/tests/oxlint/.oxlintrc-recommended.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../../oxlint-presets/recommended.json"] +} diff --git a/testdata/tests/oxlint/global-date.ts b/testdata/tests/oxlint/global-date.ts new file mode 100644 index 00000000..4c890ca3 --- /dev/null +++ b/testdata/tests/oxlint/global-date.ts @@ -0,0 +1 @@ +export const now = Date.now() diff --git a/testdata/tests/oxlint/smoke.mjs b/testdata/tests/oxlint/smoke.mjs index 3f56bd35..b6a1f412 100644 --- a/testdata/tests/oxlint/smoke.mjs +++ b/testdata/tests/oxlint/smoke.mjs @@ -12,6 +12,7 @@ assert.ok(existsSync(tsgolint), `tsgolint executable does not exist: ${tsgolint} const fixture = dirname(fileURLToPath(import.meta.url)) const oxlint = join(repositoryRoot, "oxlint", "apps", "oxlint", "dist", "cli.js") +const packageDirectory = join(repositoryRoot, "_packages", "tsgo") const env = { ...process.env, OXLINT_TSGOLINT_PATH: tsgolint @@ -23,6 +24,18 @@ const run = (...args) => spawnSync(process.execPath, [oxlint, ...args], { env }) +const packagePreset = spawnSync(process.execPath, [ + "--input-type=module", + "--eval", + `import { recommended } from "@effect/tsgo/oxlint-presets"; + import recommendedJson from "@effect/tsgo/oxlint-presets/recommended.json" with { type: "json" }; + if (recommended.rules["effecttsgo/global-date"] !== "warn" || recommendedJson.rules["effecttsgo/global-date"] !== "warn") process.exit(1);` +], { + cwd: packageDirectory, + encoding: "utf8" +}) +assert.equal(packagePreset.status, 0, packagePreset.stderr) + const rules = run("--rules", "--format", "json") assert.equal(rules.status, 0, rules.stderr) const registeredRules = JSON.parse(rules.stdout) @@ -49,4 +62,12 @@ const disabled = run("--type-aware", "--config", ".oxlintrc.json", "disabled.ts" assert.equal(disabled.status, 0, disabled.stderr) assert.doesNotMatch(`${disabled.stdout}\n${disabled.stderr}`, /effecttsgo\(floating-effect\)/) +const recommended = run("--config", ".oxlintrc-recommended.json", "global-date.ts") +assert.equal(recommended.status, 0, recommended.stderr) +assert.match(`${recommended.stdout}\n${recommended.stderr}`, /effecttsgo\(global-date\)/) + +const recommendedOverride = run("--config", ".oxlintrc-recommended-override.json", "global-date.ts") +assert.equal(recommendedOverride.status, 0, recommendedOverride.stderr) +assert.doesNotMatch(`${recommendedOverride.stdout}\n${recommendedOverride.stderr}`, /effecttsgo\(global-date\)/) + console.log("Oxlint profile smoke test passed") From 9c5582f326519bd3dceb599e7957a74fb35eb608 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:52:07 +0000 Subject: [PATCH 11/23] chore: update generated/latest --- .changeset/curly-lions-lint.md | 5 ----- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 6 ++++++ _packages/tsgo/package.json | 2 +- etscore/version_generated.go | 2 +- 18 files changed, 29 insertions(+), 14 deletions(-) delete mode 100644 .changeset/curly-lions-lint.md diff --git a/.changeset/curly-lions-lint.md b/.changeset/curly-lions-lint.md deleted file mode 100644 index 29e13ff9..00000000 --- a/.changeset/curly-lions-lint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": minor ---- - -Add recommended and category-specific shared configurations for Oxlint. diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index 47fcbb9f..d9be50ab 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index 6dedd5ac..bb9a08fc 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index 453a051a..36af3b5a 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index db1d4ef1..7f301f35 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index fdb7446f..97d29e56 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index 1f4cfb33..a793204e 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index e65faaca..effba5e4 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index c55e6c96..f849e687 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index 759848e5..95528497 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index 2d821822..a62db4e6 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index 09c625e6..d1edf40f 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index 4997c551..b259f7a1 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 7d825a77..62266425 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.36.0 + ## 0.35.0 ## 0.34.0 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index a044800b..0ceab9d0 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.35.0", + "version": "0.36.0", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index a7c492c4..cc41c547 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,11 @@ # @effect/tsgo +## 0.36.0 + +### Minor Changes + +- 8423f68: Add recommended and category-specific shared configurations for Oxlint. + ## 0.35.0 ### Minor Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 91c41b40..6b8e137e 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.35.0", + "version": "0.36.0", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/etscore/version_generated.go b/etscore/version_generated.go index 10923604..50210245 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.35.0" +const EffectVersion = "0.36.0" From 972fe0a4649b817fe7486236a592c87400f899bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 09:35:54 +0000 Subject: [PATCH 12/23] chore: update generated/latest --- .changeset/preserve-setup-json-formatting.md | 5 + _packages/tsgo/src/cli/setup/changes.ts | 87 +++++++-- .../__snapshots__/setup-cli.test.ts.snap | 172 +++++++++--------- _packages/tsgo/test/setup/changes.test.ts | 94 ++++++++++ 4 files changed, 252 insertions(+), 106 deletions(-) create mode 100644 .changeset/preserve-setup-json-formatting.md diff --git a/.changeset/preserve-setup-json-formatting.md b/.changeset/preserve-setup-json-formatting.md new file mode 100644 index 00000000..b96d08d6 --- /dev/null +++ b/.changeset/preserve-setup-json-formatting.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Preserve existing indentation and newline styles when setup updates JSON configuration files. diff --git a/_packages/tsgo/src/cli/setup/changes.ts b/_packages/tsgo/src/cli/setup/changes.ts index 1f4c9c41..e1ce5f73 100644 --- a/_packages/tsgo/src/cli/setup/changes.ts +++ b/_packages/tsgo/src/cli/setup/changes.ts @@ -90,23 +90,70 @@ function deleteNodeFromList( } } -/** - * Insert a node at the end of a list (array or object properties), handling commas properly - */ +const emptyListInsertions = new WeakMap>() + +/** + * Insert a node at the end of a list (array or object properties), handling commas properly + */ function insertNodeAtEndOfList( tracker: any, sourceFile: ts.SourceFile, nodeArray: ts.NodeArray, - newNode: T -) { - if (nodeArray.length === 0) { - tracker.insertNodeAt(sourceFile, nodeArray.pos + 1, newNode, { suffix: "\n" }) - } else { - const lastElement = nodeArray[nodeArray.length - 1] - tracker.insertNodeAt(sourceFile, lastElement.end, newNode, { prefix: ",\n" }) + newNode: T +) { + const formatOptions = getFormatOptions(sourceFile) + if (nodeArray.length === 0) { + const insertedLists = emptyListInsertions.get(tracker) ?? new WeakSet() + const hasPreviousInsertion = insertedLists.has(nodeArray) + insertedLists.add(nodeArray) + emptyListInsertions.set(tracker, insertedLists) + const closingIndentation = getIndentationAtPosition(sourceFile, nodeArray.pos, formatOptions.tabSize) + const hasFollowingLineBreak = /^[ \t]*\r?\n/.test(sourceFile.text.slice(nodeArray.pos)) + tracker.insertNodeAt(sourceFile, nodeArray.pos, newNode, { + indentation: closingIndentation.column + formatOptions.indentSize, + prefix: formatOptions.newLineCharacter, + suffix: hasPreviousInsertion + ? "," + : hasFollowingLineBreak ? "" : `${formatOptions.newLineCharacter}${closingIndentation.text}` + }) + } else { + const lastElement = nodeArray[nodeArray.length - 1] + const indentation = getIndentationAtPosition( + sourceFile, + lastElement.getStart(sourceFile), + formatOptions.tabSize + ) + tracker.insertNodeAt(sourceFile, lastElement.end, newNode, { + indentation: indentation.column, + prefix: `,${formatOptions.newLineCharacter}` + }) } } +function getFormatOptions(sourceFile: ts.SourceFile) { + const indentation = sourceFile.text.match(/^([ \t]+)\S/m)?.[1] + const indentSize = indentation?.includes("\t") ? 2 : indentation?.length ?? 2 + const newLineCharacter = sourceFile.text.includes("\r\n") ? "\r\n" : "\n" + return { + ...ts.getDefaultFormatCodeSettings(newLineCharacter), + indentSize, + tabSize: indentSize, + convertTabsToSpaces: !indentation?.includes("\t"), + newLineCharacter + } +} + +function getIndentationAtPosition(sourceFile: ts.SourceFile, position: number, tabSize: number) { + const { line } = sourceFile.getLineAndCharacterOfPosition(position) + const lineStart = sourceFile.getPositionOfLineAndCharacter(line, 0) + const text = sourceFile.text.slice(lineStart, position).match(/^[ \t]*/)?.[0] ?? "" + const column = [...text].reduce( + (column, character) => character === "\t" ? column + tabSize - (column % tabSize) : column + 1, + 0 + ) + return { column, text } +} + function findDependencyCollectionProperty( rootObj: ts.ObjectLiteralExpression, dependencyType: "dependencies" | "devDependencies" @@ -211,10 +258,10 @@ const tsInternal = ts as any /** * Create a ChangeTracker context */ -function createTrackerContext() { - const host = createMinimalHost() - const formatOptions = { indentSize: 2, tabSize: 2 } as ts.EditorSettings - const formatContext = tsInternal.formatting.getFormatContext(formatOptions, host) +function createTrackerContext(sourceFile: ts.SourceFile) { + const host = createMinimalHost() + const formatOptions = getFormatOptions(sourceFile) + const formatContext = tsInternal.formatting.getFormatContext(formatOptions, host) const preferences = {} as ts.UserPreferences return { host, formatContext, preferences } } @@ -234,7 +281,7 @@ const computePackageJsonChanges = ( return emptyFileChangesResult() } - const ctx = createTrackerContext() + const ctx = createTrackerContext(current.sourceFile) const fileChanges = tsInternal.textChanges.ChangeTracker.with( ctx, @@ -606,7 +653,7 @@ const computeTsConfigChanges = ( const schemaProperty = findPropertyInObject(rootObj, "$schema") if (!isEffectSchemaProperty(schemaProperty)) return emptyFileChangesResult() - const ctx = createTrackerContext() + const ctx = createTrackerContext(current.sourceFile) const fileChanges = tsInternal.textChanges.ChangeTracker.with(ctx, (tracker: any) => { descriptions.push("Remove $schema from tsconfig") deleteNodeFromList(tracker, current.sourceFile, rootObj.properties, schemaProperty!) @@ -628,7 +675,7 @@ const computeTsConfigChanges = ( } // Create compilerOptions with the plugin entry - const ctx = createTrackerContext() + const ctx = createTrackerContext(current.sourceFile) const fileChanges = tsInternal.textChanges.ChangeTracker.with( ctx, @@ -706,7 +753,7 @@ const computeTsConfigChanges = ( const compilerOptions = compilerOptionsProperty.initializer - const ctx = createTrackerContext() + const ctx = createTrackerContext(current.sourceFile) const fileChanges = tsInternal.textChanges.ChangeTracker.with( ctx, @@ -858,7 +905,7 @@ const computeOxlintConfigChanges = ( ts.factory.createStringLiteral("$schema"), ts.factory.createStringLiteral(schemaPath.value) ) - const ctx = createTrackerContext() + const ctx = createTrackerContext(current.sourceFile) const fileChanges = tsInternal.textChanges.ChangeTracker.with(ctx, (tracker: any) => { if (schemaProperty) { tracker.replaceNode(current.sourceFile, schemaProperty.initializer, schemaPropertyAssignment.initializer) @@ -897,7 +944,7 @@ const computeVSCodeSettingsChanges = ( return emptyFileChangesResult() } - const ctx = createTrackerContext() + const ctx = createTrackerContext(current.sourceFile) const createSettingValue = (value: unknown): ts.Expression => typeof value === "string" diff --git a/_packages/tsgo/test/setup/__snapshots__/setup-cli.test.ts.snap b/_packages/tsgo/test/setup/__snapshots__/setup-cli.test.ts.snap index 1657bf11..dc306806 100644 --- a/_packages/tsgo/test/setup/__snapshots__/setup-cli.test.ts.snap +++ b/_packages/tsgo/test/setup/__snapshots__/setup-cli.test.ts.snap @@ -25,13 +25,13 @@ exports[`Setup CLI > should add LSP plugin alongside existing plugins > package. "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should add LSP plugin alongside existing plugins > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", @@ -39,9 +39,9 @@ exports[`Setup CLI > should add LSP plugin alongside existing plugins > tsconfig { "name": "typescript-plugin-css-modules" }, -{ - "name": "@effect/language-service" -} + { + "name": "@effect/language-service" + } ] } }" @@ -94,21 +94,21 @@ exports[`Setup CLI > should add LSP with VS Code editor selected and create new "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should add LSP with VS Code editor selected and create new settings file > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service" - } -] + "plugins": [ + { + "name": "@effect/language-service" + } + ] } }" `; @@ -138,25 +138,25 @@ exports[`Setup CLI > should add LSP with custom diagnostic severities when no pl "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should add LSP with custom diagnostic severities when no plugin exists > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service", - "diagnosticSeverity": { - "floatingEffect": "warning", - "missingEffectError": "off" - } - } -] + "plugins": [ + { + "name": "@effect/language-service", + "diagnosticSeverity": { + "floatingEffect": "warning", + "missingEffectError": "off" + } + } + ] } }" `; @@ -188,24 +188,24 @@ exports[`Setup CLI > should add custom diagnostic severities when plugin exists "dependencies": {}, "devDependencies": { "@effect/tsgo": "^0.0.5", -"typescript": "7.1.0-dev.test" + "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should add custom diagnostic severities when plugin exists without custom values > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", "plugins": [ { "name": "@effect/language-service", -"diagnosticSeverity": { - "floatingEffect": "warning", - "missingEffectContext": "message" -} + "diagnosticSeverity": { + "floatingEffect": "warning", + "missingEffectContext": "message" + } } ] } @@ -242,14 +242,14 @@ exports[`Setup CLI > should generate changes for Astro-style configs using the l "devDependencies": { "@effect/language-service": "^0.80.0", "typescript": "7.1.0-dev.test", -"@effect/tsgo": "^0.0.5" + "@effect/tsgo": "^0.0.5" } }" `; exports[`Setup CLI > should generate changes for Astro-style configs using the legacy prepare command > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "extends": "astro/tsconfigs/strictest", "include": [ ".astro/types.d.ts", @@ -305,21 +305,21 @@ exports[`Setup CLI > should generate changes for adding LSP with defaults > pack "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should generate changes for adding LSP with defaults > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service" - } -] + "plugins": [ + { + "name": "@effect/language-service" + } + ] } }" `; @@ -349,22 +349,22 @@ exports[`Setup CLI > should generate changes for adding LSP with prepare script "name": "test-project", "version": "1.0.0", "dependencies": {}, -"scripts": { "prepare": "effect-tsgo patch --typescript --no-oxlint" }, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "scripts": { "prepare": "effect-tsgo patch --typescript --no-oxlint" }, + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should generate changes for adding LSP with prepare script > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service" - } -] + "plugins": [ + { + "name": "@effect/language-service" + } + ] } }" `; @@ -479,13 +479,13 @@ exports[`Setup CLI > should not override existing plugins when adding LSP plugin "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should not override existing plugins when adding LSP plugin > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", @@ -499,9 +499,9 @@ exports[`Setup CLI > should not override existing plugins when adding LSP plugin { "name": "another-typescript-plugin" }, -{ - "name": "@effect/language-service" -} + { + "name": "@effect/language-service" + } ] } }" @@ -524,10 +524,10 @@ exports[`Setup CLI > should preserve all existing VS Code settings from a real r "editor.wordBasedSuggestions": "matchingDocuments", "editor.parameterHints.enabled": true, "files.insertFinalNewline": true, -"js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"], -"js/ts.tsdk.promptToUseWorkspaceVersion": true, -"js/ts.tsdk.path": "./node_modules/typescript/bin", -"js/ts.experimental.useTsgo": true + "js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"], + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.tsdk.path": "./node_modules/typescript/bin", + "js/ts.experimental.useTsgo": true }" `; @@ -566,21 +566,21 @@ exports[`Setup CLI > should preserve all existing VS Code settings from a real r "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should preserve all existing VS Code settings from a real repository config > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service" - } -] + "plugins": [ + { + "name": "@effect/language-service" + } + ] } }" `; @@ -590,10 +590,10 @@ exports[`Setup CLI > should preserve existing VS Code settings when adding LSP-s "editor.formatOnSave": true, "editor.tabSize": 2, "files.autoSave": "onFocusChange", -"js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"], -"js/ts.tsdk.promptToUseWorkspaceVersion": true, -"js/ts.tsdk.path": "./node_modules/typescript/bin", -"js/ts.experimental.useTsgo": true + "js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"], + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.tsdk.path": "./node_modules/typescript/bin", + "js/ts.experimental.useTsgo": true }" `; @@ -632,21 +632,21 @@ exports[`Setup CLI > should preserve existing VS Code settings when adding LSP-s "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should preserve existing VS Code settings when adding LSP-specific settings > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service" - } -] + "plugins": [ + { + "name": "@effect/language-service" + } + ] } }" `; @@ -766,7 +766,7 @@ exports[`Setup CLI > should replace existing tsconfig schema when adding LSP > p "name": "test-project", "version": "1.0.0", "dependencies": {}, -"devDependencies": { "@effect/tsgo": "^0.0.5","typescript": "7.1.0-dev.test" } + "devDependencies": { "@effect/tsgo": "^0.0.5", "typescript": "7.1.0-dev.test" } }" `; @@ -776,11 +776,11 @@ exports[`Setup CLI > should replace existing tsconfig schema when adding LSP > t "compilerOptions": { "strict": true, "target": "ES2022", -"plugins": [ - { - "name": "@effect/language-service" - } -] + "plugins": [ + { + "name": "@effect/language-service" + } + ] } }" `; @@ -819,7 +819,7 @@ exports[`Setup CLI > should update LSP version when already installed with older exports[`Setup CLI > should update LSP version when already installed with older version > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", @@ -859,14 +859,14 @@ exports[`Setup CLI > should update custom diagnostic severities when plugin alre "dependencies": {}, "devDependencies": { "@effect/tsgo": "^0.0.5", -"typescript": "7.1.0-dev.test" + "typescript": "7.1.0-dev.test" } }" `; exports[`Setup CLI > should update custom diagnostic severities when plugin already has custom values > tsconfig.json 1`] = ` "{ - "$schema": "./node_modules/@effect/tsgo/schema.json", + "$schema": "./node_modules/@effect/tsgo/schema.json", "compilerOptions": { "strict": true, "target": "ES2022", @@ -874,10 +874,10 @@ exports[`Setup CLI > should update custom diagnostic severities when plugin alre { "name": "@effect/language-service", "diagnosticSeverity": { - "floatingEffect": "warning", - "missingEffectError": "off", - "missingLayerContext": "error" - } + "floatingEffect": "warning", + "missingEffectError": "off", + "missingLayerContext": "error" + } } ] } diff --git a/_packages/tsgo/test/setup/changes.test.ts b/_packages/tsgo/test/setup/changes.test.ts index b7564d38..b9470b06 100644 --- a/_packages/tsgo/test/setup/changes.test.ts +++ b/_packages/tsgo/test/setup/changes.test.ts @@ -985,4 +985,98 @@ describe("computeChanges", () => { expect(rendered).not.toContain("diagnosticSeverity") }) }) + + describe("JSON indentation", () => { + it.each([ + ["two spaces", " ", "\n"], + ["four spaces", " ", "\n"], + ["tabs", "\t", "\n"], + ["CRLF newlines", " ", "\r\n"] + ])("should preserve %s in package.json", (_, indentation, newLine) => { + const packageJsonText = [ + "{", + `${indentation}"name": "test-project",`, + `${indentation}"devDependencies": {`, + `${indentation.repeat(2)}"typescript": "${TEST_TYPESCRIPT_VERSION}"`, + `${indentation}}`, + "}" + ].join(newLine) + const result = runComputeChanges({ + packageJsonText, + typescriptVersion: null, + prepareScript: false, + editors: [], + vscodeTargetSettings: null + }) + const change = result.codeActions + .flatMap((action) => action.changes) + .find((change) => change.fileName === "/test/package.json") + const updated = applyTextChanges(packageJsonText, change?.textChanges ?? []) + + expect(updated).toContain(`${newLine}${indentation.repeat(2)}"@effect/tsgo": "0.0.4"`) + expect(() => JSON.parse(updated)).not.toThrow() + }) + + it.each([ + ["two spaces", " "], + ["four spaces", " "], + ["tabs", "\t"] + ])("should preserve %s in tsconfig.json", (_, indentation) => { + const tsconfigText = [ + "{", + `${indentation}"compilerOptions": {`, + `${indentation.repeat(2)}"strict": true`, + `${indentation}}`, + "}" + ].join("\n") + const result = runComputeChanges({ + tsconfigText, + prepareScript: false, + editors: [], + vscodeTargetSettings: null + }) + const change = result.codeActions + .flatMap((action) => action.changes) + .find((change) => change.fileName === "/test/tsconfig.json") + const updated = applyTextChanges(tsconfigText, change?.textChanges ?? []) + + expect(updated).toContain(`\n${indentation}"$schema"`) + expect(updated).toContain(`\n${indentation.repeat(2)}"plugins"`) + expect(updated).toContain(`\n${indentation.repeat(4)}"name": "@effect/language-service"`) + expect(() => JSON.parse(updated)).not.toThrow() + }) + + it.each([ + { layout: "inline", lines: [" \"devDependencies\": {}"] }, + { layout: "multiline", lines: [" \"devDependencies\": {", " }"] } + ])("should indent properties added to an $layout empty object", ({ lines }) => { + const packageJsonText = [ + "{", + " \"name\": \"test-project\",", + ...lines, + "}" + ].join("\n") + const result = runComputeChanges({ + packageJsonText, + prepareScript: false, + editors: [], + vscodeTargetSettings: null + }) + const change = result.codeActions + .flatMap((action) => action.changes) + .find((change) => change.fileName === "/test/package.json") + const updated = applyTextChanges(packageJsonText, change?.textChanges ?? []) + + expect(updated).toBe([ + "{", + " \"name\": \"test-project\",", + " \"devDependencies\": {", + ` "typescript": "${TEST_TYPESCRIPT_VERSION}",`, + " \"@effect/tsgo\": \"0.0.4\"", + " }", + "}" + ].join("\n")) + expect(() => JSON.parse(updated)).not.toThrow() + }) + }) }) From fbdc66626f73e9670c068ac58ccdef7727dfd695 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 11:02:09 +0000 Subject: [PATCH 13/23] chore: update generated/latest --- .changeset/update-upstreams.md | 5 +++++ _packages/tsgo/upstream.json | 6 +++--- flake.lock | 8 ++++---- flake.nix | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 .changeset/update-upstreams.md diff --git a/.changeset/update-upstreams.md b/.changeset/update-upstreams.md new file mode 100644 index 00000000..656ebd55 --- /dev/null +++ b/.changeset/update-upstreams.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260808.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/24fabe95acba758c05fcb349bf427a3a0c8ad676) commit `24fabe95acba758c05fcb349bf427a3a0c8ad676`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2). diff --git a/_packages/tsgo/upstream.json b/_packages/tsgo/upstream.json index bb4e2810..33629f44 100644 --- a/_packages/tsgo/upstream.json +++ b/_packages/tsgo/upstream.json @@ -3,7 +3,7 @@ "tags": { "typescript": { "latest": "7.0.2", - "next": "7.1.0-dev.20260806.1" + "next": "7.1.0-dev.20260808.1" }, "oxlint": { "latest": "1.77.0" @@ -17,8 +17,8 @@ "7.0.2": { "gitHead": "2bd066d87f5bafd315be9f40889d0a60b9e58e0b" }, - "7.1.0-dev.20260806.1": { - "gitHead": "86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee" + "7.1.0-dev.20260808.1": { + "gitHead": "24fabe95acba758c05fcb349bf427a3a0c8ad676" } }, "oxlint-tsgolint": { diff --git a/flake.lock b/flake.lock index bf1a6c50..bc945402 100644 --- a/flake.lock +++ b/flake.lock @@ -43,17 +43,17 @@ "typescript-go-src": { "flake": false, "locked": { - "lastModified": 1785946683, - "narHash": "sha256-g8BHkacd00IkM9+b9bUhQrn3CnfhwnnVmouEkce1+pE=", + "lastModified": 1786142966, + "narHash": "sha256-bTxCaH2cjDU46/xQTScFgHbEeGBy7Omv87FCj22wVxs=", "owner": "microsoft", "repo": "typescript-go", - "rev": "86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee", + "rev": "24fabe95acba758c05fcb349bf427a3a0c8ad676", "type": "github" }, "original": { "owner": "microsoft", "repo": "typescript-go", - "rev": "86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee", + "rev": "24fabe95acba758c05fcb349bf427a3a0c8ad676", "type": "github" } }, diff --git a/flake.nix b/flake.nix index b3712ae5..67efc1a1 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,7 @@ nixpkgsUnstable.url = "github:NixOS/nixpkgs/nixos-unstable"; /* Source of truth: the next profile in `_packages/tsgo/upstream.json`. */ typescript-go-src = { - url = "github:microsoft/typescript-go/86cc4767d4ebadb9b7845d0ab8eb2b05785c3fee?submodules=1"; + url = "github:microsoft/typescript-go/24fabe95acba758c05fcb349bf427a3a0c8ad676?submodules=1"; flake = false; }; /* Derived from the selected TypeScript-Go revision and recorded in the manifest. */ From 81b0e36d49704f7018233b88d965798cbf9f213c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 11:34:16 +0000 Subject: [PATCH 14/23] chore: update generated/latest --- .changeset/preserve-setup-json-formatting.md | 5 ----- .changeset/update-upstreams.md | 5 ----- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 7 +++++++ _packages/tsgo/package.json | 2 +- etscore/version_generated.go | 2 +- 19 files changed, 30 insertions(+), 19 deletions(-) delete mode 100644 .changeset/preserve-setup-json-formatting.md delete mode 100644 .changeset/update-upstreams.md diff --git a/.changeset/preserve-setup-json-formatting.md b/.changeset/preserve-setup-json-formatting.md deleted file mode 100644 index b96d08d6..00000000 --- a/.changeset/preserve-setup-json-formatting.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Preserve existing indentation and newline styles when setup updates JSON configuration files. diff --git a/.changeset/update-upstreams.md b/.changeset/update-upstreams.md deleted file mode 100644 index 656ebd55..00000000 --- a/.changeset/update-upstreams.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260808.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/24fabe95acba758c05fcb349bf427a3a0c8ad676) commit `24fabe95acba758c05fcb349bf427a3a0c8ad676`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2). diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index d9be50ab..fda068e6 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index bb9a08fc..06952cd1 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index 36af3b5a..c7c26a44 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index 7f301f35..a8d1ec2d 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index 97d29e56..0665f1e3 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index a793204e..c02c52b3 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index effba5e4..c16d25bb 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index f849e687..8de207cc 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index 95528497..c2391f1d 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index a62db4e6..18179ebe 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index d1edf40f..47a7cefd 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index b259f7a1..a84dc7f3 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 62266425..629657ae 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.36.1 + ## 0.36.0 ## 0.35.0 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index 0ceab9d0..d65912a6 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.36.0", + "version": "0.36.1", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index cc41c547..f962b237 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/tsgo +## 0.36.1 + +### Patch Changes + +- 4db1d4b: Preserve existing indentation and newline styles when setup updates JSON configuration files. +- c154a04: Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260808.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/24fabe95acba758c05fcb349bf427a3a0c8ad676) commit `24fabe95acba758c05fcb349bf427a3a0c8ad676`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2). + ## 0.36.0 ### Minor Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 6b8e137e..3e299ecc 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.36.0", + "version": "0.36.1", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/etscore/version_generated.go b/etscore/version_generated.go index 50210245..5ac83054 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.36.0" +const EffectVersion = "0.36.1" From 2eccd1e71ccf2c6f13fd180cea9e46411eb1fd0f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:02:32 +0000 Subject: [PATCH 15/23] chore: update generated/latest --- .changeset/skip-invisible-severity-rules.md | 7 +++ etscheckerhooks/init.go | 3 +- etscore/severity.go | 26 ++++++++ etscore/severity_test.go | 38 +++++++++++ etsoxlintrunner/runner.go | 4 +- internal/directives/parser.go | 38 +++++++++++ internal/directives/parser_test.go | 70 +++++++++++++++++++++ internal/rulerunner/diagnostics.go | 41 +++++++++--- internal/rulerunner/diagnostics_test.go | 43 +++++++++++++ 9 files changed, 257 insertions(+), 13 deletions(-) create mode 100644 .changeset/skip-invisible-severity-rules.md create mode 100644 etscore/severity_test.go create mode 100644 internal/rulerunner/diagnostics_test.go diff --git a/.changeset/skip-invisible-severity-rules.md b/.changeset/skip-invisible-severity-rules.md new file mode 100644 index 00000000..03a7e9e2 --- /dev/null +++ b/.changeset/skip-invisible-severity-rules.md @@ -0,0 +1,7 @@ +--- +"@effect/tsgo": patch +--- + +Skip diagnostic rules below the minimum visible severity before executing them. + +In `tsc` CLI mode without `includeSuggestionsInTsc`, suggestion- and message-severity diagnostics are dropped from the output after rules run. The rule runner now receives the minimum severity the caller can surface and skips such rules up front, avoiding their type-checker queries entirely. A rule below the threshold still runs when any directive in the file references it (for example `// @effect-diagnostics ruleName:error` or a wildcard), since directives can raise its severity and must be tracked for `unusedDirective` reporting. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes roughly 1–2s of check time. diff --git a/etscheckerhooks/init.go b/etscheckerhooks/init.go index 255b70e9..823cf13d 100644 --- a/etscheckerhooks/init.go +++ b/etscheckerhooks/init.go @@ -32,7 +32,8 @@ func getEffectConfig(p checker.Program) *etscore.EffectPluginOptions { // afterCheckSourceFile is called after type checking each source file. // It runs Effect diagnostics if the plugin is enabled. func afterCheckSourceFile(ctx context.Context, program checker.Program, c *checker.Checker, sf *ast.SourceFile) { - diagnostics, err := rulerunner.Run(ctx, program, c, sf, getEffectConfig(program), nil) + effectConfig := getEffectConfig(program) + diagnostics, err := rulerunner.Run(ctx, program, c, sf, effectConfig, nil, rulerunner.MinVisibleSeverity(effectConfig)) if err != nil { return } diff --git a/etscore/severity.go b/etscore/severity.go index 09ecb42e..cf8214ac 100644 --- a/etscore/severity.go +++ b/etscore/severity.go @@ -63,6 +63,32 @@ func (s Severity) IsOff() bool { return s == SeverityOff || s == SeveritySkipFile } +// visibilityRank orders severities by how prominently they surface in output: +// error > warning > suggestion > message > off/skip-file. This is distinct +// from the declaration order of the constants, which is not a visibility +// ordering. +func (s Severity) visibilityRank() int { + switch s { + case SeverityError: + return 4 + case SeverityWarning: + return 3 + case SeveritySuggestion: + return 2 + case SeverityMessage: + return 1 + default: + return 0 + } +} + +// AtLeastAsVisibleAs reports whether s surfaces at least as prominently as +// min. Off and skip-file severities are never at least as visible as any +// enabled severity. +func (s Severity) AtLeastAsVisibleAs(min Severity) bool { + return s.visibilityRank() >= min.visibilityRank() +} + // MarshalJSON implements json.Marshaler for Severity. // Serializes as the string representation (e.g., "error", "warning"). func (s Severity) MarshalJSON() ([]byte, error) { diff --git a/etscore/severity_test.go b/etscore/severity_test.go new file mode 100644 index 00000000..9600d884 --- /dev/null +++ b/etscore/severity_test.go @@ -0,0 +1,38 @@ +package etscore + +import "testing" + +func TestSeverityAtLeastAsVisibleAs(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + severity Severity + min Severity + expected bool + }{ + {"error meets warning threshold", SeverityError, SeverityWarning, true}, + {"warning meets warning threshold", SeverityWarning, SeverityWarning, true}, + {"suggestion below warning threshold", SeveritySuggestion, SeverityWarning, false}, + {"message below warning threshold", SeverityMessage, SeverityWarning, false}, + {"off below warning threshold", SeverityOff, SeverityWarning, false}, + {"skip-file below warning threshold", SeveritySkipFile, SeverityWarning, false}, + {"error meets message threshold", SeverityError, SeverityMessage, true}, + {"warning meets message threshold", SeverityWarning, SeverityMessage, true}, + {"suggestion meets message threshold", SeveritySuggestion, SeverityMessage, true}, + {"message meets message threshold", SeverityMessage, SeverityMessage, true}, + {"off below message threshold", SeverityOff, SeverityMessage, false}, + {"suggestion below error threshold", SeveritySuggestion, SeverityError, false}, + {"warning below error threshold", SeverityWarning, SeverityError, false}, + {"error meets error threshold", SeverityError, SeverityError, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := tt.severity.AtLeastAsVisibleAs(tt.min); got != tt.expected { + t.Errorf("%v.AtLeastAsVisibleAs(%v) = %v, want %v", tt.severity, tt.min, got, tt.expected) + } + }) + } +} diff --git a/etsoxlintrunner/runner.go b/etsoxlintrunner/runner.go index 09633895..9a7dbcde 100644 --- a/etsoxlintrunner/runner.go +++ b/etsoxlintrunner/runner.go @@ -65,7 +65,7 @@ func RunRule( ) ([]*ast.Diagnostic, error) { normalized := normalizeOptions(options, ruleName) - return rulerunner.Run(ctx, program, c, sf, &normalized, []string{ruleName}) + return rulerunner.Run(ctx, program, c, sf, &normalized, []string{ruleName}, rulerunner.MinVisibleSeverity(&normalized)) } func normalizeOptions(options *etscore.EffectPluginOptions, ruleName string) etscore.EffectPluginOptions { @@ -103,7 +103,7 @@ func RunRuleAndReport( return errors.New("diagnostic adapter Report callback is required") } normalized := normalizeOptions(options, ruleName) - diagnostics, err := rulerunner.Run(ctx, program, c, sf, &normalized, []string{ruleName}) + diagnostics, err := rulerunner.Run(ctx, program, c, sf, &normalized, []string{ruleName}, rulerunner.MinVisibleSeverity(&normalized)) if err != nil { return err } diff --git a/internal/directives/parser.go b/internal/directives/parser.go index 71d59430..bf5f5d91 100644 --- a/internal/directives/parser.go +++ b/internal/directives/parser.go @@ -320,6 +320,44 @@ func (ds *DirectiveSet) HasEnablingDirective(ruleName string) bool { return false } +// HasAnyDirectiveForRule returns true if any directive in the file references +// the given rule, either by name or via a wildcard, regardless of the severity +// it assigns. This includes file-level, section, and next-line directives. It +// is used to decide whether a rule below the minimum visible severity can be +// safely skipped pre-execution: when a directive mentions the rule, running it +// may be required either to honor a severity override or to mark the directive +// as used for unusedDirective tracking. +func (ds *DirectiveSet) HasAnyDirectiveForRule(ruleName string) bool { + ruleLower := strings.ToLower(ruleName) + + matches := func(rules []RuleSeverity) bool { + for _, rs := range rules { + ruleNameLower := strings.ToLower(rs.Rule) + if ruleNameLower == ruleLower || ruleNameLower == "*" { + return true + } + } + return false + } + + if matches(ds.fileLevel) { + return true + } + for _, sd := range ds.sectionDirectives { + if matches(sd.Rules) { + return true + } + } + for _, directives := range ds.byLine { + for _, d := range directives { + if matches(d.Rules) { + return true + } + } + } + return false +} + // GetUnusedNextLineDirectives returns next-line directives that did not suppress any diagnostic. // This is used to report unusedDirective warnings. func (ds *DirectiveSet) GetUnusedNextLineDirectives(allDirectives []Directive) []Directive { diff --git a/internal/directives/parser_test.go b/internal/directives/parser_test.go index c943b80d..29de6f22 100644 --- a/internal/directives/parser_test.go +++ b/internal/directives/parser_test.go @@ -924,3 +924,73 @@ Effect.succeed(1)` t.Errorf("HasEnablingDirective(\"floatingEffect\") = %v, want false (wildcard off should not enable)", result) } } + +func TestHasAnyDirectiveForRule(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + source string + ruleName string + expected bool + }{ + { + name: "no directives", + source: "Effect.succeed(1)", + ruleName: "floatingEffect", + expected: false, + }, + { + name: "section directive mentions rule by name", + source: "// @effect-diagnostics floatingEffect:error\nEffect.succeed(1)", + ruleName: "floatingEffect", + expected: true, + }, + { + name: "section directive mentions other rule only", + source: "// @effect-diagnostics pocRule:error\nEffect.succeed(1)", + ruleName: "floatingEffect", + expected: false, + }, + { + name: "wildcard section directive mentions every rule", + source: "// @effect-diagnostics *:error\nEffect.succeed(1)", + ruleName: "floatingEffect", + expected: true, + }, + { + name: "lowering directive still counts as a mention", + source: "// @effect-diagnostics floatingEffect:off\nEffect.succeed(1)", + ruleName: "floatingEffect", + expected: true, + }, + { + name: "next-line directive counts as a mention", + source: "// @effect-diagnostics-next-line floatingEffect:off\nEffect.log(\"x\")", + ruleName: "floatingEffect", + expected: true, + }, + { + name: "skip-file directive counts as a mention", + source: "// @effect-diagnostics floatingEffect:skip-file", + ruleName: "floatingEffect", + expected: true, + }, + { + name: "rule name matching is case-insensitive", + source: "// @effect-diagnostics FLOATINGeffect:error\nEffect.succeed(1)", + ruleName: "floatingEffect", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ds := BuildDirectiveSet(CollectEffectDirectives(tt.source)) + if got := ds.HasAnyDirectiveForRule(tt.ruleName); got != tt.expected { + t.Errorf("HasAnyDirectiveForRule(%q) = %v, want %v", tt.ruleName, got, tt.expected) + } + }) + } +} diff --git a/internal/rulerunner/diagnostics.go b/internal/rulerunner/diagnostics.go index 6e8950cb..4bf8e591 100644 --- a/internal/rulerunner/diagnostics.go +++ b/internal/rulerunner/diagnostics.go @@ -25,8 +25,22 @@ type RuleDiagnostic struct { Diagnostic *ast.Diagnostic } +// MinVisibleSeverity returns the least visible severity that can surface in +// the current execution mode: in CLI (tsc) mode without includeSuggestionsInTsc +// only warnings and errors are printed, so SeverityWarning; everywhere else +// (LSP, tests) every severity surfaces, so SeverityMessage. +func MinVisibleSeverity(effectConfig *etscore.EffectPluginOptions) etscore.Severity { + if etscore.IsCommandLineMode() && !effectConfig.GetIncludeSuggestionsInTsc() { + return etscore.SeverityWarning + } + return etscore.SeverityMessage +} + // Run executes Effect diagnostics for a source file and returns diagnostics without emitting them. -func Run(ctx context.Context, program checker.Program, c *checker.Checker, sf *ast.SourceFile, effectConfig *etscore.EffectPluginOptions, ruleNames []string) ([]*ast.Diagnostic, error) { +// minSeverity is the least visible severity the caller can surface (see +// MinVisibleSeverity); rules whose configured severity is below it are skipped +// pre-execution unless a directive in the file references them. +func Run(ctx context.Context, program checker.Program, c *checker.Checker, sf *ast.SourceFile, effectConfig *etscore.EffectPluginOptions, ruleNames []string, minSeverity etscore.Severity) ([]*ast.Diagnostic, error) { if sf.IsDeclarationFile || program.IsSourceFileFromExternalLibrary(sf) { return nil, nil } @@ -70,8 +84,8 @@ func Run(ctx context.Context, program checker.Program, c *checker.Checker, sf *a return nil, nil } - allDiagnostics := collectDiagnostics(ctx, program, c, tp, sf, effectConfig, effectiveConfig, resolvedSeverity, directiveSet, selectedRules) - finalDiagnostics := transformDiagnostics(allDiagnostics, sf, directiveSet, effectConfig, resolvedSeverity) + allDiagnostics := collectDiagnostics(ctx, program, c, tp, sf, effectConfig, effectiveConfig, resolvedSeverity, directiveSet, selectedRules, minSeverity) + finalDiagnostics := transformDiagnostics(allDiagnostics, sf, directiveSet, resolvedSeverity, minSeverity) finalDiagnostics = append(finalDiagnostics, unusedDirectiveDiagnostics(sf, effectDirectives, directiveSet, resolvedSeverity)...) return finalDiagnostics, nil @@ -113,6 +127,7 @@ func collectDiagnostics( resolvedSeverity map[string]etscore.Severity, directiveSet *directives.DirectiveSet, selectedRules []*rule.Rule, + minSeverity etscore.Severity, ) []*RuleDiagnostic { var results []*RuleDiagnostic @@ -124,6 +139,15 @@ func collectDiagnostics( if !globalConfig.SkipDisabledOptimization && configSeverity.IsOff() && !directiveSet.HasEnablingDirective(r.Name) { continue } + // A rule below the minimum visible severity can only surface if a + // directive raises it, and it must still run when any directive + // references it so the directive is marked used for unusedDirective + // tracking. + if !globalConfig.SkipDisabledOptimization && + !configSeverity.AtLeastAsVisibleAs(minSeverity) && + !directiveSet.HasAnyDirectiveForRule(r.Name) { + continue + } if directiveSet.IsSkipFile(r.Name) { continue @@ -148,8 +172,8 @@ func transformDiagnostics( diags []*RuleDiagnostic, sf *ast.SourceFile, directiveSet *directives.DirectiveSet, - globalConfig *etscore.EffectPluginOptions, resolvedSeverity map[string]etscore.Severity, + minSeverity etscore.Severity, ) []*ast.Diagnostic { var results []*ast.Diagnostic lineMap := sf.ECMALineMap() @@ -171,16 +195,13 @@ func transformDiagnostics( if effectiveSeverity.IsOff() { continue } + if !effectiveSeverity.AtLeastAsVisibleAs(minSeverity) { + continue + } originalCategory := rd.Diagnostic.Category() newCategory := directives.ToCategory(effectiveSeverity) - if etscore.IsCommandLineMode() && !globalConfig.GetIncludeSuggestionsInTsc() { - if newCategory == tsdiag.CategorySuggestion || newCategory == tsdiag.CategoryMessage { - continue - } - } - if originalCategory != newCategory { results = append(results, createTransformedDiagnostic(rd.Diagnostic, newCategory)) } else { diff --git a/internal/rulerunner/diagnostics_test.go b/internal/rulerunner/diagnostics_test.go new file mode 100644 index 00000000..bdff8e95 --- /dev/null +++ b/internal/rulerunner/diagnostics_test.go @@ -0,0 +1,43 @@ +package rulerunner + +import ( + "testing" + + "github.com/effect-ts/tsgo/etscore" +) + +// Not parallel: EnterCommandLineMode toggles process-global state. +func TestMinVisibleSeverity(t *testing.T) { //nolint:paralleltest + includeSuggestions := &etscore.EffectPluginOptions{IncludeSuggestionsInTsc: true} + dropSuggestions := &etscore.EffectPluginOptions{IncludeSuggestionsInTsc: false} + + t.Run("outside CLI mode every severity is visible", func(t *testing.T) { + if got := MinVisibleSeverity(dropSuggestions); got != etscore.SeverityMessage { + t.Errorf("MinVisibleSeverity = %v, want %v", got, etscore.SeverityMessage) + } + }) + + t.Run("CLI mode without includeSuggestionsInTsc only surfaces warnings and errors", func(t *testing.T) { + restore := etscore.EnterCommandLineMode() + defer restore() + if got := MinVisibleSeverity(dropSuggestions); got != etscore.SeverityWarning { + t.Errorf("MinVisibleSeverity = %v, want %v", got, etscore.SeverityWarning) + } + }) + + t.Run("CLI mode with includeSuggestionsInTsc keeps every severity", func(t *testing.T) { + restore := etscore.EnterCommandLineMode() + defer restore() + if got := MinVisibleSeverity(includeSuggestions); got != etscore.SeverityMessage { + t.Errorf("MinVisibleSeverity = %v, want %v", got, etscore.SeverityMessage) + } + }) + + t.Run("CLI mode with nil config keeps every severity", func(t *testing.T) { + restore := etscore.EnterCommandLineMode() + defer restore() + if got := MinVisibleSeverity(nil); got != etscore.SeverityMessage { + t.Errorf("MinVisibleSeverity = %v, want %v", got, etscore.SeverityMessage) + } + }) +} From eda09d72a0bf88fd3ce6939bb5174ee68f702b42 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:45:22 +0000 Subject: [PATCH 16/23] chore: update generated/latest --- .changeset/walker-rule-effect-prefilter.md | 7 + internal/rules/effect_in_failure.go | 8 + internal/rules/promise_in_effect_success.go | 10 ++ internal/typeparser/could_be_strict_effect.go | 113 +++++++++++++ .../typeparser/could_be_strict_effect_test.go | 159 ++++++++++++++++++ 5 files changed, 297 insertions(+) create mode 100644 .changeset/walker-rule-effect-prefilter.md create mode 100644 internal/typeparser/could_be_strict_effect.go create mode 100644 internal/typeparser/could_be_strict_effect_test.go diff --git a/.changeset/walker-rule-effect-prefilter.md b/.changeset/walker-rule-effect-prefilter.md new file mode 100644 index 00000000..090106c5 --- /dev/null +++ b/.changeset/walker-rule-effect-prefilter.md @@ -0,0 +1,7 @@ +--- +"@effect/tsgo": patch +--- + +Skip flow-analysis type queries for references that conclusively cannot be an Effect. + +The `effectInFailure` and `promiseInEffectSuccess` rules walk every node of a file and query its flow type just to test whether it is a strict Effect type. The new `TypeParser.NodeCouldBeStrictEffect` prefilter inspects the referenced symbol's declared type first — flow narrowing can only refine the declared type, so a declared type that conclusively contains no possibly-Effect constituent (primitives, plain objects with a different type name, unions thereof) can never produce a strict Effect flow type, and the expensive query is skipped. The predicate is conservative: `any`/`unknown`, type parameters, conditionals, symbol-less types, and deep unions always fall through to the full query. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes ~10% of build wall time (~2.7s of ~26.8s). diff --git a/internal/rules/effect_in_failure.go b/internal/rules/effect_in_failure.go index 7a268e88..534589f7 100644 --- a/internal/rules/effect_in_failure.go +++ b/internal/rules/effect_in_failure.go @@ -60,6 +60,14 @@ var EffectInFailure = rule.Rule{ continue } + // Declared-type prefilter: skip the expensive flow-analysis query + // for reference nodes that conclusively cannot have a strict + // Effect flow type. Skipped nodes can never match, so no + // shouldSkip bookkeeping is needed. + if !ctx.TypeParser.NodeCouldBeStrictEffect(node) { + continue + } + nodeType := ctx.TypeParser.GetTypeAtLocation(node) if nodeType == nil { continue diff --git a/internal/rules/promise_in_effect_success.go b/internal/rules/promise_in_effect_success.go index 63da0f37..bfa80805 100644 --- a/internal/rules/promise_in_effect_success.go +++ b/internal/rules/promise_in_effect_success.go @@ -61,6 +61,16 @@ var PromiseInEffectSuccess = rule.Rule{ continue } + // Declared-type prefilter: a diagnostic requires a strict Effect + // flow type on the node, which reference nodes with a + // conclusively non-Effect declared type can never have. Skipped + // nodes can never match, so no matched-map bookkeeping is needed. + // Call expressions are unaffected: the prefilter only rules out + // identifiers and property accesses. + if !ctx.TypeParser.NodeCouldBeStrictEffect(node) { + continue + } + t := ctx.TypeParser.GetTypeAtLocation(node) if node.Kind == ast.KindCallExpression { if signature := ctx.Checker.GetResolvedSignature(node); signature != nil { diff --git a/internal/typeparser/could_be_strict_effect.go b/internal/typeparser/could_be_strict_effect.go new file mode 100644 index 00000000..0e8ca92a --- /dev/null +++ b/internal/typeparser/could_be_strict_effect.go @@ -0,0 +1,113 @@ +package typeparser + +import ( + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" +) + +// strictEffectTypeNames are the type symbol names that StrictEffectType can +// match. couldBeNamed takes a set so future prefilters for other wrapper +// types (Stream, Layer, ...) can reuse the same conservative walk. +var strictEffectTypeNames = map[string]bool{"Effect": true} + +// NodeCouldBeStrictEffect reports whether node's flow type could possibly be +// a strict Effect type (a type whose symbol is named "Effect", see +// StrictEffectType). For reference nodes (identifiers and property accesses) +// it inspects the referenced symbol's declared type, which is cheap compared +// to the flow analysis performed by GetTypeAtLocation. Flow narrowing can +// only refine the declared type — select union constituents, narrow +// any/unknown, or intersect it — so a declared type that conclusively +// contains no possibly-Effect constituent can never produce a strict-Effect +// flow type. +// +// It returns true ("cannot rule out") for every other node kind and whenever +// the answer is not conclusively negative, so whole-file walker rules may use +// a false result to skip expensive GetTypeAtLocation queries without ever +// missing a strict Effect type. +func (tp *TypeParser) NodeCouldBeStrictEffect(node *ast.Node) bool { + if tp == nil || tp.checker == nil || node == nil { + return true + } + if node.Kind != ast.KindIdentifier && node.Kind != ast.KindPropertyAccessExpression { + return true + } + sym := tp.ReferenceSymbolAtNode(node) + if sym == nil { + return true + } + return tp.SymbolCouldBeStrictEffect(sym) +} + +// SymbolCouldBeStrictEffect reports whether a reference to sym could possibly +// have a strict-Effect flow type, based on the symbol's declared type only. +func (tp *TypeParser) SymbolCouldBeStrictEffect(sym *ast.Symbol) bool { + if tp == nil || tp.checker == nil || sym == nil { + return true + } + declared := tp.getTypeOfSymbolSafe(sym) + return tp.CouldBeStrictEffect(declared) +} + +// getTypeOfSymbolSafe wraps Checker.GetTypeOfSymbol with a panic guard, +// returning nil (treated as inconclusive by callers) on any checker panic. +func (tp *TypeParser) getTypeOfSymbolSafe(sym *ast.Symbol) (result *checker.Type) { + defer func() { + if r := recover(); r != nil { + result = nil + } + }() + return tp.checker.GetTypeOfSymbol(sym) +} + +// CouldBeStrictEffect reports whether flow narrowing starting from declared +// type t could ever produce a strict Effect type. It is deliberately +// conservative: it only returns false when t is conclusively non-Effect (a +// primitive/never type, or a plain object type with a non-nil symbol whose +// name is not "Effect"). Any/unknown, type variables, unions, intersections +// and symbol-less types all return true. +func (tp *TypeParser) CouldBeStrictEffect(t *checker.Type) bool { + return couldBeNamed(t, strictEffectTypeNames, 0) +} + +// couldBeNamed reports whether flow narrowing starting from declared type t +// could ever produce a type whose symbol name is in names. False only on a +// conclusive negative. +func couldBeNamed(t *checker.Type, names map[string]bool, depth int) bool { + if t == nil { + return true + } + flags := t.Flags() + if flags&checker.TypeFlagsAnyOrUnknown != 0 { + return true + } + if flags&checker.TypeFlagsUnionOrIntersection != 0 { + if depth > 4 { + return true + } + for _, member := range t.Types() { + if couldBeNamed(member, names, depth+1) { + return true + } + } + return false + } + // Type parameters, indexed accesses, conditionals, substitutions, etc. + // can instantiate to anything. + if flags&checker.TypeFlagsInstantiable != 0 { + return true + } + // Primitives and never can never narrow to an object type. + if flags&(checker.TypeFlagsPrimitive|checker.TypeFlagsNever) != 0 { + return false + } + // Anything that is not a plain object type at this point is unexpected; + // stay conservative. + if flags&checker.TypeFlagsObject == 0 { + return true + } + sym := t.Symbol() + if sym == nil { + return true + } + return names[sym.Name] +} diff --git a/internal/typeparser/could_be_strict_effect_test.go b/internal/typeparser/could_be_strict_effect_test.go new file mode 100644 index 00000000..a7c45a3e --- /dev/null +++ b/internal/typeparser/could_be_strict_effect_test.go @@ -0,0 +1,159 @@ +package typeparser + +import ( + "strings" + "testing" + + "github.com/effect-ts/tsgo/internal/bundledeffect" + "github.com/microsoft/typescript-go/shim/ast" +) + +// findIdentifierByName returns the first identifier node with the given text +// that is not a declaration name. +func findIdentifierByName(t *testing.T, sf *ast.SourceFile, name string) *ast.Node { + t.Helper() + var found *ast.Node + var visit func(node *ast.Node) bool + visit = func(node *ast.Node) bool { + if found != nil { + return true + } + if node.Kind == ast.KindIdentifier && node.Text() == name && !ast.IsDeclarationName(node) { + found = node + return true + } + node.ForEachChild(visit) + return false + } + sf.AsNode().ForEachChild(visit) + if found == nil { + t.Fatalf("identifier %q not found in source", name) + } + return found +} + +func TestNodeCouldBeStrictEffect(t *testing.T) { + t.Parallel() + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { + t.Skip("Effect v4 not installed:", err) + } + + source := ` +import { Effect } from "effect" + +const anEffect = Effect.succeed(1) +const aString = "hello" +const aNumber = 42 +const anAny: any = null +const anUnknown: unknown = null +const aUnionWithEffect: Effect.Effect | string = anEffect +const aUnionWithoutEffect: string | number | boolean = "x" +interface Plain { readonly value: number } +const aPlainObject: Plain = { value: 1 } +const anObjectKeyword: object = { value: 1 } +function generic(param: T): T { return param } + +export const uses = [ + anEffect, + aString, + aNumber, + anAny, + anUnknown, + aUnionWithEffect, + aUnionWithoutEffect, + aPlainObject, + anObjectKeyword, +] +export function inner(param: T) { return [param] } +` + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV4Internal(t, source) + defer done() + + tests := []struct { + identifier string + expected bool + }{ + // Conclusive negatives: the declared type can never flow-narrow into + // a type whose symbol is named "Effect". + {"aString", false}, + {"aNumber", false}, + {"aUnionWithoutEffect", false}, + {"aPlainObject", false}, + // Effect references and everything inconclusive must stay true. + {"anEffect", true}, + {"anAny", true}, + {"anUnknown", true}, + {"aUnionWithEffect", true}, + {"anObjectKeyword", true}, // the object keyword type has no symbol + {"param", true}, // type parameters can instantiate to anything + } + + // Subtests deliberately avoided: all cases share one checker, which is + // not safe for the parallel subtests the tparallel linter would require. + for _, tt := range tests { + node := findIdentifierByName(t, sf, tt.identifier) + if got := tp.NodeCouldBeStrictEffect(node); got != tt.expected { + t.Errorf("NodeCouldBeStrictEffect(%s) = %v, want %v", tt.identifier, got, tt.expected) + } + } + + // Non-reference node kinds are never ruled out. + var call *ast.Node + var visit func(node *ast.Node) bool + visit = func(node *ast.Node) bool { + if call != nil { + return true + } + if node.Kind == ast.KindCallExpression { + call = node + return true + } + node.ForEachChild(visit) + return false + } + sf.AsNode().ForEachChild(visit) + if call == nil { + t.Fatal("no call expression found") + } + if !tp.NodeCouldBeStrictEffect(call) { + t.Error("call expressions must not be ruled out by the reference prefilter") + } + + // Nil receiver and nil node stay conservative. + var nilTp *TypeParser + if !nilTp.NodeCouldBeStrictEffect(nil) { + t.Error("nil TypeParser must not rule anything out") + } + if !tp.NodeCouldBeStrictEffect(nil) { + t.Error("nil node must not be ruled out") + } +} + +func TestCouldBeStrictEffectDeepUnionStaysConservative(t *testing.T) { + t.Parallel() + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { + t.Skip("Effect v4 not installed:", err) + } + + // A union nested beyond the recursion depth limit must return true even + // though every member is a primitive literal. + members := make([]string, 0, 40) + for _, s := range []string{"a", "b", "c", "d", "e", "f", "g", "h"} { + members = append(members, `"lit`+s+`"`) + } + source := ` +type Deep = ` + strings.Join(members, " | ") + ` +const deepValue: Deep = "lita" +export const use = [deepValue] +` + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV4Internal(t, source) + defer done() + + node := findIdentifierByName(t, sf, "deepValue") + // Literal unions are flat, so this exercises the union walk; whatever the + // nesting, the answer may be false only when provably safe — a flat + // primitive union is provably safe. + if tp.NodeCouldBeStrictEffect(node) { + t.Error("flat primitive literal union should be conclusively non-Effect") + } +} From c64a2ee59dbd4d7c0f9509c194ae896cbcf1718f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 18:43:34 +0000 Subject: [PATCH 17/23] chore: update generated/latest --- .changeset/call-signature-effect-prefilter.md | 7 ++ internal/rules/promise_in_effect_success.go | 13 ++- internal/typeparser/could_be_strict_effect.go | 24 +++++ .../typeparser/could_be_strict_effect_test.go | 101 ++++++++++++++++-- 4 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 .changeset/call-signature-effect-prefilter.md diff --git a/.changeset/call-signature-effect-prefilter.md b/.changeset/call-signature-effect-prefilter.md new file mode 100644 index 00000000..6f13de31 --- /dev/null +++ b/.changeset/call-signature-effect-prefilter.md @@ -0,0 +1,7 @@ +--- +"@effect/tsgo": patch +--- + +Extend the walker-rule prefilter to call expressions. + +A call expression's type is its resolved signature's return type, and both the signature and that return type are already cached from the main check phase. `NodeCouldBeStrictEffect` now consults them for call nodes and skips the expensive flow-analysis re-check when the declared return type conclusively cannot be a strict Effect. Signature-less calls, optional chains, and every inconclusive return type stay conservative, and `promiseInEffectSuccess` no longer computes a location type for calls only to discard it. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes a further ~4.6% of wall time on top of the reference-node prefilter, bringing the total Effect diagnostics overhead versus a pristine tsgo build of the same commit down to ~17%. diff --git a/internal/rules/promise_in_effect_success.go b/internal/rules/promise_in_effect_success.go index bfa80805..6b14105f 100644 --- a/internal/rules/promise_in_effect_success.go +++ b/internal/rules/promise_in_effect_success.go @@ -63,20 +63,23 @@ var PromiseInEffectSuccess = rule.Rule{ // Declared-type prefilter: a diagnostic requires a strict Effect // flow type on the node, which reference nodes with a - // conclusively non-Effect declared type can never have. Skipped - // nodes can never match, so no matched-map bookkeeping is needed. - // Call expressions are unaffected: the prefilter only rules out - // identifiers and property accesses. + // conclusively non-Effect declared type — and calls whose + // resolved signature conclusively cannot return one — can never + // have. Skipped nodes can never match, so no matched-map + // bookkeeping is needed. if !ctx.TypeParser.NodeCouldBeStrictEffect(node) { continue } - t := ctx.TypeParser.GetTypeAtLocation(node) + var t *checker.Type if node.Kind == ast.KindCallExpression { if signature := ctx.Checker.GetResolvedSignature(node); signature != nil { t = ctx.Checker.GetReturnTypeOfSignature(signature) } } + if t == nil { + t = ctx.TypeParser.GetTypeAtLocation(node) + } effect := ctx.TypeParser.StrictEffectType(t, node) if effect == nil || !typeContainsPromise(ctx.TypeParser, effect.A) { continue diff --git a/internal/typeparser/could_be_strict_effect.go b/internal/typeparser/could_be_strict_effect.go index 0e8ca92a..9f6b22b0 100644 --- a/internal/typeparser/could_be_strict_effect.go +++ b/internal/typeparser/could_be_strict_effect.go @@ -28,6 +28,9 @@ func (tp *TypeParser) NodeCouldBeStrictEffect(node *ast.Node) bool { if tp == nil || tp.checker == nil || node == nil { return true } + if node.Kind == ast.KindCallExpression { + return tp.callCouldReturnStrictEffect(node) + } if node.Kind != ast.KindIdentifier && node.Kind != ast.KindPropertyAccessExpression { return true } @@ -38,6 +41,27 @@ func (tp *TypeParser) NodeCouldBeStrictEffect(node *ast.Node) bool { return tp.SymbolCouldBeStrictEffect(sym) } +// callCouldReturnStrictEffect reports whether a call expression's type could +// possibly be a strict Effect type, based on the return type of its resolved +// signature. The signature and its return type are cached from the main check +// phase, so consulting them is cheap compared to re-checking the call via +// GetTypeAtLocation. A call expression's type is its resolved signature's +// return type (union-widened with undefined for optional chains, which the +// conservative union walk handles), so a conclusively non-Effect declared +// return type rules the node out. +func (tp *TypeParser) callCouldReturnStrictEffect(node *ast.Node) (result bool) { + defer func() { + if r := recover(); r != nil { + result = true + } + }() + signature := tp.checker.GetResolvedSignature(node) + if signature == nil { + return true + } + return couldBeNamed(tp.checker.GetReturnTypeOfSignature(signature), strictEffectTypeNames, 0) +} + // SymbolCouldBeStrictEffect reports whether a reference to sym could possibly // have a strict-Effect flow type, based on the symbol's declared type only. func (tp *TypeParser) SymbolCouldBeStrictEffect(sym *ast.Symbol) bool { diff --git a/internal/typeparser/could_be_strict_effect_test.go b/internal/typeparser/could_be_strict_effect_test.go index a7c45a3e..9a140bca 100644 --- a/internal/typeparser/could_be_strict_effect_test.go +++ b/internal/typeparser/could_be_strict_effect_test.go @@ -6,6 +6,7 @@ import ( "github.com/effect-ts/tsgo/internal/bundledeffect" "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/scanner" ) // findIdentifierByName returns the first identifier node with the given text @@ -97,26 +98,27 @@ export function inner(param: T) { return [param] } } } - // Non-reference node kinds are never ruled out. - var call *ast.Node + // Other non-reference node kinds (e.g. binary expressions) are never + // ruled out. + var binary *ast.Node var visit func(node *ast.Node) bool visit = func(node *ast.Node) bool { - if call != nil { + if binary != nil { return true } - if node.Kind == ast.KindCallExpression { - call = node + if node.Kind == ast.KindArrayLiteralExpression { + binary = node return true } node.ForEachChild(visit) return false } sf.AsNode().ForEachChild(visit) - if call == nil { - t.Fatal("no call expression found") + if binary == nil { + t.Fatal("no array literal expression found") } - if !tp.NodeCouldBeStrictEffect(call) { - t.Error("call expressions must not be ruled out by the reference prefilter") + if !tp.NodeCouldBeStrictEffect(binary) { + t.Error("non-reference, non-call node kinds must not be ruled out") } // Nil receiver and nil node stay conservative. @@ -157,3 +159,84 @@ export const use = [deepValue] t.Error("flat primitive literal union should be conclusively non-Effect") } } + +// findCallByCalleeName returns the first call expression whose callee text +// contains the given substring. +func findCallByCalleeName(t *testing.T, sf *ast.SourceFile, callee string) *ast.Node { + t.Helper() + var found *ast.Node + var visit func(node *ast.Node) bool + visit = func(node *ast.Node) bool { + if found != nil { + return true + } + if node.Kind == ast.KindCallExpression { + expr := node.AsCallExpression().Expression + if expr != nil && strings.Contains(scanner.GetTextOfNode(expr), callee) { + found = node + return true + } + } + node.ForEachChild(visit) + return false + } + sf.AsNode().ForEachChild(visit) + if found == nil { + t.Fatalf("call to %q not found in source", callee) + } + return found +} + +func TestCallCouldReturnStrictEffect(t *testing.T) { + t.Parallel() + if err := bundledeffect.EnsurePackageInstalled(bundledeffect.EffectV4, "effect"); err != nil { + t.Skip("Effect v4 not installed:", err) + } + + source := ` +import { Effect } from "effect" + +declare function makesEffect(): Effect.Effect +declare function makesString(): string +declare function makesUnion(flag: boolean): Effect.Effect | undefined +declare function makesAny(): any +declare function generic(value: T): T +declare const maybe: { makesEffect(): Effect.Effect } | undefined + +export const uses = [ + makesEffect(), + makesString(), + makesUnion(true), + makesAny(), + generic("x"), + maybe?.makesEffect(), +] +` + _, tp, sf, done := compileAndGetCheckerAndSourceFileWithEffectV4Internal(t, source) + defer done() + + // Subtests deliberately avoided: all cases share one checker, which is + // not safe for the parallel subtests the tparallel linter would require. + tests := []struct { + callee string + expected bool + }{ + // Conclusive negative: the declared return type can never be Effect. + {"makesString", false}, + // Effect-returning calls and every inconclusive case stay true. + {"makesEffect", true}, + {"makesUnion", true}, // union containing Effect + {"makesAny", true}, // any return + // The resolved signature is instantiated, so generic("x") conclusively + // returns the primitive literal "x" and is ruled out. + {"generic", false}, + {"maybe?.makesEffect", true}, // optional chain: Effect | undefined union + } + + for _, tt := range tests { + call := findCallByCalleeName(t, sf, tt.callee) + if got := tp.NodeCouldBeStrictEffect(call); got != tt.expected { + t.Errorf("NodeCouldBeStrictEffect(call %s) = %v, want %v", tt.callee, got, tt.expected) + } + } +} From 6a00b9f019d3ad38b52186e0cf5d89e017c2540e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 06:26:34 +0000 Subject: [PATCH 18/23] chore: update generated/latest --- .changeset/call-signature-effect-prefilter.md | 7 ------- .changeset/skip-invisible-severity-rules.md | 7 ------- .changeset/walker-rule-effect-prefilter.md | 7 ------- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 14 ++++++++++++++ _packages/tsgo/package.json | 2 +- etscore/version_generated.go | 2 +- 20 files changed, 37 insertions(+), 30 deletions(-) delete mode 100644 .changeset/call-signature-effect-prefilter.md delete mode 100644 .changeset/skip-invisible-severity-rules.md delete mode 100644 .changeset/walker-rule-effect-prefilter.md diff --git a/.changeset/call-signature-effect-prefilter.md b/.changeset/call-signature-effect-prefilter.md deleted file mode 100644 index 6f13de31..00000000 --- a/.changeset/call-signature-effect-prefilter.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Extend the walker-rule prefilter to call expressions. - -A call expression's type is its resolved signature's return type, and both the signature and that return type are already cached from the main check phase. `NodeCouldBeStrictEffect` now consults them for call nodes and skips the expensive flow-analysis re-check when the declared return type conclusively cannot be a strict Effect. Signature-less calls, optional chains, and every inconclusive return type stay conservative, and `promiseInEffectSuccess` no longer computes a location type for calls only to discard it. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes a further ~4.6% of wall time on top of the reference-node prefilter, bringing the total Effect diagnostics overhead versus a pristine tsgo build of the same commit down to ~17%. diff --git a/.changeset/skip-invisible-severity-rules.md b/.changeset/skip-invisible-severity-rules.md deleted file mode 100644 index 03a7e9e2..00000000 --- a/.changeset/skip-invisible-severity-rules.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Skip diagnostic rules below the minimum visible severity before executing them. - -In `tsc` CLI mode without `includeSuggestionsInTsc`, suggestion- and message-severity diagnostics are dropped from the output after rules run. The rule runner now receives the minimum severity the caller can surface and skips such rules up front, avoiding their type-checker queries entirely. A rule below the threshold still runs when any directive in the file references it (for example `// @effect-diagnostics ruleName:error` or a wildcard), since directives can raise its severity and must be tracked for `unusedDirective` reporting. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes roughly 1–2s of check time. diff --git a/.changeset/walker-rule-effect-prefilter.md b/.changeset/walker-rule-effect-prefilter.md deleted file mode 100644 index 090106c5..00000000 --- a/.changeset/walker-rule-effect-prefilter.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Skip flow-analysis type queries for references that conclusively cannot be an Effect. - -The `effectInFailure` and `promiseInEffectSuccess` rules walk every node of a file and query its flow type just to test whether it is a strict Effect type. The new `TypeParser.NodeCouldBeStrictEffect` prefilter inspects the referenced symbol's declared type first — flow narrowing can only refine the declared type, so a declared type that conclusively contains no possibly-Effect constituent (primitives, plain objects with a different type name, unions thereof) can never produce a strict Effect flow type, and the expensive query is skipped. The predicate is conservative: `any`/`unknown`, type parameters, conditionals, symbol-less types, and deep unions always fall through to the full query. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes ~10% of build wall time (~2.7s of ~26.8s). diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index fda068e6..dbf3f23a 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index 06952cd1..be057fbf 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index c7c26a44..1da13cb6 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index a8d1ec2d..d691f4d1 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index 0665f1e3..945a7d64 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index c02c52b3..d0618feb 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index c16d25bb..cc5093b8 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index 8de207cc..0e4cf9df 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index c2391f1d..b31027ba 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index 18179ebe..4d1cfe8f 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index 47a7cefd..29cf1f58 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index a84dc7f3..8bb16cf0 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 629657ae..101e89b7 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.36.2 + ## 0.36.1 ## 0.36.0 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index d65912a6..7ff0bfaa 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.36.1", + "version": "0.36.2", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index f962b237..f2d74841 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,19 @@ # @effect/tsgo +## 0.36.2 + +### Patch Changes + +- 8362acc: Extend the walker-rule prefilter to call expressions. + + A call expression's type is its resolved signature's return type, and both the signature and that return type are already cached from the main check phase. `NodeCouldBeStrictEffect` now consults them for call nodes and skips the expensive flow-analysis re-check when the declared return type conclusively cannot be a strict Effect. Signature-less calls, optional chains, and every inconclusive return type stay conservative, and `promiseInEffectSuccess` no longer computes a location type for calls only to discard it. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes a further ~4.6% of wall time on top of the reference-node prefilter, bringing the total Effect diagnostics overhead versus a pristine tsgo build of the same commit down to ~17%. +- 9020153: Skip diagnostic rules below the minimum visible severity before executing them. + + In `tsc` CLI mode without `includeSuggestionsInTsc`, suggestion- and message-severity diagnostics are dropped from the output after rules run. The rule runner now receives the minimum severity the caller can surface and skips such rules up front, avoiding their type-checker queries entirely. A rule below the threshold still runs when any directive in the file references it (for example `// @effect-diagnostics ruleName:error` or a wildcard), since directives can raise its severity and must be tracked for `unusedDirective` reporting. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes roughly 1–2s of check time. +- 257af25: Skip flow-analysis type queries for references that conclusively cannot be an Effect. + + The `effectInFailure` and `promiseInEffectSuccess` rules walk every node of a file and query its flow type just to test whether it is a strict Effect type. The new `TypeParser.NodeCouldBeStrictEffect` prefilter inspects the referenced symbol's declared type first — flow narrowing can only refine the declared type, so a declared type that conclusively contains no possibly-Effect constituent (primitives, plain objects with a different type name, unions thereof) can never produce a strict Effect flow type, and the expensive query is skipped. The predicate is conservative: `any`/`unknown`, type parameters, conditionals, symbol-less types, and deep unions always fall through to the full query. Emitted diagnostics are unchanged; on a large Effect monorepo build this removes ~10% of build wall time (~2.7s of ~26.8s). + ## 0.36.1 ### Patch Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 3e299ecc..2c8833f4 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.36.1", + "version": "0.36.2", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/etscore/version_generated.go b/etscore/version_generated.go index 5ac83054..31bde5d8 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.36.1" +const EffectVersion = "0.36.2" From f3a394f5a865cf7a80d081794e5bd4563364a6a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:07:03 +0000 Subject: [PATCH 19/23] chore: update generated/latest --- .changeset/update-effect-beta-107.md | 5 ++ _packages/tsgo/package.json | 6 +- _tools/repoctl/package.json | 4 +- pnpm-lock.yaml | 82 +++++++++---------- .../schemaMutableKey_ts2322.errors.txt | 5 +- ...2322.flows.schemaMutableKey_ts2322.mermaid | 29 +++---- .../schemaMutableKey_ts2322.pipings.txt | 4 +- ...utableKey_ts2322_pluginDisabled.errors.txt | 5 +- ...maMutableKey_ts2322_pluginDisabled.mermaid | 29 +++---- ...tableKey_ts2322_pluginDisabled.pipings.txt | 4 +- .../effect-v4-document-symbols/package.json | 2 +- .../tests/effect-v4-refactors/package.json | 2 +- testdata/tests/effect-v4/package.json | 4 +- .../effect-v4/schemaMutableKey_ts2322.ts | 5 +- .../schemaMutableKey_ts2322_pluginDisabled.ts | 5 +- testdata/tests/oxlint/package.json | 2 +- 16 files changed, 106 insertions(+), 87 deletions(-) create mode 100644 .changeset/update-effect-beta-107.md diff --git a/.changeset/update-effect-beta-107.md b/.changeset/update-effect-beta-107.md new file mode 100644 index 00000000..a9b0c149 --- /dev/null +++ b/.changeset/update-effect-beta-107.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Update Effect v4 dependencies and embedded test fixtures to `4.0.0-beta.107`. diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 2c8833f4..5866146c 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -58,12 +58,12 @@ "@effect/tsgo-darwin-arm64": "workspace:*" }, "devDependencies": { - "@effect/platform-node": "^4.0.0-beta.104", - "@effect/platform-node-shared": "^4.0.0-beta.104", + "@effect/platform-node": "^4.0.0-beta.107", + "@effect/platform-node-shared": "^4.0.0-beta.107", "@types/node": "^24.3.0", "tsdown": "^0.20.1", "typescript": "^5.9.2", - "effect": "^4.0.0-beta.104", + "effect": "^4.0.0-beta.107", "vitest": "^3.2.1" } } diff --git a/_tools/repoctl/package.json b/_tools/repoctl/package.json index e051219c..e8b20e0d 100644 --- a/_tools/repoctl/package.json +++ b/_tools/repoctl/package.json @@ -11,8 +11,8 @@ "test": "node --test test/*.test.ts" }, "dependencies": { - "@effect/platform-node": "^4.0.0-beta.104", - "effect": "^4.0.0-beta.104", + "@effect/platform-node": "^4.0.0-beta.107", + "effect": "^4.0.0-beta.107", "semver": "^7.7.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1755ea6c..bc02b93f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,17 +18,17 @@ importers: _packages/tsgo: devDependencies: '@effect/platform-node': - specifier: ^4.0.0-beta.104 - version: 4.0.0-beta.104(effect@4.0.0-beta.104)(ioredis@5.9.2) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.2) '@effect/platform-node-shared': - specifier: ^4.0.0-beta.104 - version: 4.0.0-beta.104(effect@4.0.0-beta.104) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107) '@types/node': specifier: ^24.3.0 version: 24.10.13 effect: - specifier: ^4.0.0-beta.104 - version: 4.0.0-beta.104 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 tsdown: specifier: ^0.20.1 version: 0.20.3(typescript@5.9.3) @@ -78,11 +78,11 @@ importers: _tools/repoctl: dependencies: '@effect/platform-node': - specifier: ^4.0.0-beta.104 - version: 4.0.0-beta.104(effect@4.0.0-beta.104)(ioredis@5.9.2) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.2) effect: - specifier: ^4.0.0-beta.104 - version: 4.0.0-beta.104 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 semver: specifier: ^7.7.2 version: 7.8.5 @@ -145,8 +145,8 @@ importers: testdata/tests/effect-v4: dependencies: '@effect/vitest': - specifier: 4.0.0-beta.104 - version: 4.0.0-beta.104(effect@4.0.0-beta.104)(vitest@4.1.10(@types/node@22.19.15)(vite@7.3.1(@types/node@22.19.15)(yaml@2.9.0))) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10(@types/node@22.19.15)(vite@7.3.1(@types/node@22.19.15)(yaml@2.9.0))) '@standard-schema/spec': specifier: ^1.1.0 version: 1.1.0 @@ -157,8 +157,8 @@ importers: specifier: 4.1.10 version: 4.1.10 effect: - specifier: 4.0.0-beta.104 - version: 4.0.0-beta.104 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 fast-check: specifier: ^4.4.0 version: 4.5.3 @@ -178,8 +178,8 @@ importers: specifier: ^22.0.0 version: 22.19.15 effect: - specifier: 4.0.0-beta.104 - version: 4.0.0-beta.104 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 fast-check: specifier: ^4.4.0 version: 4.5.3 @@ -196,8 +196,8 @@ importers: specifier: ^22.0.0 version: 22.19.15 effect: - specifier: 4.0.0-beta.104 - version: 4.0.0-beta.104 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 fast-check: specifier: ^4.4.0 version: 4.5.3 @@ -208,8 +208,8 @@ importers: testdata/tests/oxlint: dependencies: effect: - specifier: 4.0.0-beta.104 - version: 4.0.0-beta.104 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 packages: @@ -303,18 +303,18 @@ packages: resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} engines: {node: '>= 20.12.0'} - '@effect/platform-node-shared@4.0.0-beta.104': - resolution: {integrity: sha512-wZQWlreuCfR+Ip6E93h2Wo56lFdBTflzSErFKxvrAW06/ORa7yZMD4oq4Ow5aU5+AlQgomQSEeBKM7uao4ZewQ==} + '@effect/platform-node-shared@4.0.0-beta.107': + resolution: {integrity: sha512-y6BqcRi86BfTJv+tvDrob4ozYVHxxlHYcn/zIQqZjXI9CvKnkgD6ng+38G1o45c4f2ucU+6HRI9POCmFdMoVGA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.104 + effect: ^4.0.0-beta.107 - '@effect/platform-node@4.0.0-beta.104': - resolution: {integrity: sha512-edqD0sRzL3Qow26TnD+RuHNVolFB7c1M/xWGzW0TRc7PzXpRWQawM1haMXOrB8W1UXpg8T8jF5MU56vFhPGJyw==} + '@effect/platform-node@4.0.0-beta.107': + resolution: {integrity: sha512-k+6YNbV4Ck0L6YXtlgkvEnuP5tlxWD8EeWOrpn46PDqbGEwt4ONpRltTwm3tn2cyBXD0i+2P11cUH/6sdFagTA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.104 - ioredis: ^5.7.0 + effect: ^4.0.0-beta.107 + ioredis: '>=5.7.0 <6.0.0' '@effect/vitest@0.27.0': resolution: {integrity: sha512-8bM7n9xlMUYw9GqPIVgXFwFm2jf27m/R7psI64PGpwU5+26iwyxp9eAXEsfT5S6lqztYfpQQ1Ubp5o6HfNYzJQ==} @@ -322,11 +322,11 @@ packages: effect: ^3.19.0 vitest: ^3.2.0 - '@effect/vitest@4.0.0-beta.104': - resolution: {integrity: sha512-09AvNl3tJNR7hOEnxE8TqSlBTeqfrWu/s4mIjlv1q6c/r6izkH/qhUrByP87ebbEtCMPJDHHXNcKFs3kEbugbQ==} + '@effect/vitest@4.0.0-beta.107': + resolution: {integrity: sha512-n4/qsx4DnT4dEI/wNgMivxyUeJoeiU1TCSz0WnoHWk/dny40Oxjip2P9IXGQDgPb9fsYVnerF0QRA6nPUuExQA==} peerDependencies: - effect: ^4.0.0-beta.104 - vitest: ^4.1.0 + effect: ^4.0.0-beta.107 + vitest: '>=4.1.0 <5.0.0' '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -952,8 +952,8 @@ packages: effect@3.19.19: resolution: {integrity: sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg==} - effect@4.0.0-beta.104: - resolution: {integrity: sha512-YSSaaMc8gBoHnabYXlgHpKVctsj4ezTSoojdd8SA3NWHoZ7LMPiUDhCnP1ZSOfQ7ly6P6XLhAw216NfLEHfg2A==} + effect@4.0.0-beta.107: + resolution: {integrity: sha512-OoBAv8eF+yanc+C6xhgEUnWeXUSHA6ynnscYqpkAY9GSnzZWystsIjBowVqCkLpHGlnRtdIqYT3wHwpOY6JDnQ==} empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} @@ -1582,19 +1582,19 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@effect/platform-node-shared@4.0.0-beta.104(effect@4.0.0-beta.104)': + '@effect/platform-node-shared@4.0.0-beta.107(effect@4.0.0-beta.107)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.104 + effect: 4.0.0-beta.107 ws: 8.21.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.104(effect@4.0.0-beta.104)(ioredis@5.9.2)': + '@effect/platform-node@4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.2)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.104(effect@4.0.0-beta.104) - effect: 4.0.0-beta.104 + '@effect/platform-node-shared': 4.0.0-beta.107(effect@4.0.0-beta.107) + effect: 4.0.0-beta.107 ioredis: 5.9.2 mime: 4.1.0 undici: 8.9.0 @@ -1607,9 +1607,9 @@ snapshots: effect: 3.19.19 vitest: 3.2.4(@types/node@22.19.15)(yaml@2.9.0) - '@effect/vitest@4.0.0-beta.104(effect@4.0.0-beta.104)(vitest@4.1.10(@types/node@22.19.15)(vite@7.3.1(@types/node@22.19.15)(yaml@2.9.0)))': + '@effect/vitest@4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10(@types/node@22.19.15)(vite@7.3.1(@types/node@22.19.15)(yaml@2.9.0)))': dependencies: - effect: 4.0.0-beta.104 + effect: 4.0.0-beta.107 vitest: 4.1.10(@types/node@22.19.15)(vite@7.3.1(@types/node@22.19.15)(yaml@2.9.0)) '@emnapi/core@1.8.1': @@ -2063,7 +2063,7 @@ snapshots: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - effect@4.0.0-beta.104: + effect@4.0.0-beta.107: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 4.9.0 diff --git a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.errors.txt b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.errors.txt index b4be7406..73f7eb97 100644 --- a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.errors.txt +++ b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.errors.txt @@ -4,7 +4,10 @@ Effect version: 4.0.0 ==== /.src/schemaMutableKey_ts2322.ts (0 errors) ==== - import { Schema, Struct as Struct_, SchemaAST } from "effect" + import { Schema, Struct as Struct_, SchemaAST } from "effect" + declare module "effect/SchemaAST" { + export function optionalKey(ast: A): A + } interface optionalKeyLambda extends Struct_.Lambda { diff --git a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.flows.schemaMutableKey_ts2322.mermaid b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.flows.schemaMutableKey_ts2322.mermaid index 41694078..aa7cdf89 100644 --- a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.flows.schemaMutableKey_ts2322.mermaid +++ b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.flows.schemaMutableKey_ts2322.mermaid @@ -1,15 +1,16 @@ flowchart TB - 0[/"type:
node: Struct_.Lambda"/] - 1[["type: #40;schema: Top#41; =#gt; optionalKey#lt;Top#gt;
node: #40;schema#41; =#gt; Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"]] - 2[/"type: optionalKey#lt;Top#gt;
node: Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"/] - 3[/"type: AST
node: schema.ast"/] - 4["type: AST
callee: SchemaAST.optionalKey
args: #91;#93;"] - 5[/"type: #lt;A extends AST#gt;#40;ast: A#41; =#gt; A
node: SchemaAST.optionalKey"/] - 6["type: optionalKeyLambda
callee: Struct_.lambda
args: #91;#93;"] - 7[/"type: #lt;L extends #40;a: any#41; =#gt; any#gt;#40;f: #40;a: Parameters#lt;L#gt;#91;0#93;#41; =#gt; ReturnType#lt;L#gt;#41; =#gt; L
node: Struct_.lambda"/] - 3 -->|"kind: pipe"| 4 - 5 -->|"kind: transformCallee"| 4 - 4 -->|"kind: usedBy"| 2 - 2 -->|"kind: potentialReturn"| 1 - 1 -->|"kind: pipe"| 6 - 7 -->|"kind: transformCallee"| 6 \ No newline at end of file + 0[["type: #lt;A extends SchemaAST.AST#gt;#40;ast: A#41; =#gt; A
node: export function optionalKey#lt;A extends SchemaAST.AST#gt;#40;ast: A#41;: A"]] + 1[/"type:
node: Struct_.Lambda"/] + 2[["type: #40;schema: Top#41; =#gt; optionalKey#lt;Top#gt;
node: #40;schema#41; =#gt; Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"]] + 3[/"type: optionalKey#lt;Top#gt;
node: Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"/] + 4[/"type: AST
node: schema.ast"/] + 5["type: AST
callee: SchemaAST.optionalKey
args: #91;#93;"] + 6[/"type: #lt;A extends SchemaAST.AST#gt;#40;ast: A#41; =#gt; A
node: SchemaAST.optionalKey"/] + 7["type: optionalKeyLambda
callee: Struct_.lambda
args: #91;#93;"] + 8[/"type: #lt;L extends #40;a: any#41; =#gt; any#gt;#40;f: #40;a: Parameters#lt;L#gt;#91;0#93;#41; =#gt; ReturnType#lt;L#gt;#41; =#gt; L
node: Struct_.lambda"/] + 4 -->|"kind: pipe"| 5 + 6 -->|"kind: transformCallee"| 5 + 5 -->|"kind: usedBy"| 3 + 3 -->|"kind: potentialReturn"| 2 + 2 -->|"kind: pipe"| 7 + 8 -->|"kind: transformCallee"| 7 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.pipings.txt b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.pipings.txt index 1882a4e1..4971c764 100644 --- a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.pipings.txt +++ b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322.pipings.txt @@ -1,7 +1,7 @@ ==== /.src/schemaMutableKey_ts2322.ts (2 flows) ==== === Piping Flow === -Location: 30:27 - 30:133 +Location: 33:27 - 33:133 Node: Struct_.lambda((schema) => Schema.make(SchemaAST.optionalKey(schema.ast), { schema })) Node Kind: KindCallExpression @@ -15,7 +15,7 @@ Transformations (1): outType: optionalKeyLambda === Piping Flow === -Location: 30:86 - 30:119 +Location: 33:86 - 33:119 Node: SchemaAST.optionalKey(schema.ast) Node Kind: KindCallExpression diff --git a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.errors.txt b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.errors.txt index 2ff778af..b80aae37 100644 --- a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.errors.txt +++ b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.errors.txt @@ -15,7 +15,10 @@ Effect version: 4.0.0 ==== /.src/schemaMutableKey_ts2322_pluginDisabled.ts (0 errors) ==== - import { Schema, Struct as Struct_, SchemaAST } from "effect" + import { Schema, Struct as Struct_, SchemaAST } from "effect" + declare module "effect/SchemaAST" { + export function optionalKey
(ast: A): A + } interface optionalKeyLambda extends Struct_.Lambda { diff --git a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.flows.schemaMutableKey_ts2322_pluginDisabled.mermaid b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.flows.schemaMutableKey_ts2322_pluginDisabled.mermaid index 41694078..aa7cdf89 100644 --- a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.flows.schemaMutableKey_ts2322_pluginDisabled.mermaid +++ b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.flows.schemaMutableKey_ts2322_pluginDisabled.mermaid @@ -1,15 +1,16 @@ flowchart TB - 0[/"type:
node: Struct_.Lambda"/] - 1[["type: #40;schema: Top#41; =#gt; optionalKey#lt;Top#gt;
node: #40;schema#41; =#gt; Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"]] - 2[/"type: optionalKey#lt;Top#gt;
node: Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"/] - 3[/"type: AST
node: schema.ast"/] - 4["type: AST
callee: SchemaAST.optionalKey
args: #91;#93;"] - 5[/"type: #lt;A extends AST#gt;#40;ast: A#41; =#gt; A
node: SchemaAST.optionalKey"/] - 6["type: optionalKeyLambda
callee: Struct_.lambda
args: #91;#93;"] - 7[/"type: #lt;L extends #40;a: any#41; =#gt; any#gt;#40;f: #40;a: Parameters#lt;L#gt;#91;0#93;#41; =#gt; ReturnType#lt;L#gt;#41; =#gt; L
node: Struct_.lambda"/] - 3 -->|"kind: pipe"| 4 - 5 -->|"kind: transformCallee"| 4 - 4 -->|"kind: usedBy"| 2 - 2 -->|"kind: potentialReturn"| 1 - 1 -->|"kind: pipe"| 6 - 7 -->|"kind: transformCallee"| 6 \ No newline at end of file + 0[["type: #lt;A extends SchemaAST.AST#gt;#40;ast: A#41; =#gt; A
node: export function optionalKey#lt;A extends SchemaAST.AST#gt;#40;ast: A#41;: A"]] + 1[/"type:
node: Struct_.Lambda"/] + 2[["type: #40;schema: Top#41; =#gt; optionalKey#lt;Top#gt;
node: #40;schema#41; =#gt; Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"]] + 3[/"type: optionalKey#lt;Top#gt;
node: Schema.make#40;SchemaAST.optionalKey#40;schema.ast#41;, #123; schema #125;#41;"/] + 4[/"type: AST
node: schema.ast"/] + 5["type: AST
callee: SchemaAST.optionalKey
args: #91;#93;"] + 6[/"type: #lt;A extends SchemaAST.AST#gt;#40;ast: A#41; =#gt; A
node: SchemaAST.optionalKey"/] + 7["type: optionalKeyLambda
callee: Struct_.lambda
args: #91;#93;"] + 8[/"type: #lt;L extends #40;a: any#41; =#gt; any#gt;#40;f: #40;a: Parameters#lt;L#gt;#91;0#93;#41; =#gt; ReturnType#lt;L#gt;#41; =#gt; L
node: Struct_.lambda"/] + 4 -->|"kind: pipe"| 5 + 6 -->|"kind: transformCallee"| 5 + 5 -->|"kind: usedBy"| 3 + 3 -->|"kind: potentialReturn"| 2 + 2 -->|"kind: pipe"| 7 + 8 -->|"kind: transformCallee"| 7 \ No newline at end of file diff --git a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.pipings.txt b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.pipings.txt index 32521831..336f52b8 100644 --- a/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.pipings.txt +++ b/testdata/baselines/reference/effect-v4/schemaMutableKey_ts2322_pluginDisabled.pipings.txt @@ -1,7 +1,7 @@ ==== /.src/schemaMutableKey_ts2322_pluginDisabled.ts (2 flows) ==== === Piping Flow === -Location: 30:27 - 30:133 +Location: 33:27 - 33:133 Node: Struct_.lambda((schema) => Schema.make(SchemaAST.optionalKey(schema.ast), { schema })) Node Kind: KindCallExpression @@ -15,7 +15,7 @@ Transformations (1): outType: optionalKeyLambda === Piping Flow === -Location: 30:86 - 30:119 +Location: 33:86 - 33:119 Node: SchemaAST.optionalKey(schema.ast) Node Kind: KindCallExpression diff --git a/testdata/tests/effect-v4-document-symbols/package.json b/testdata/tests/effect-v4-document-symbols/package.json index 88a5ad70..71d47e69 100644 --- a/testdata/tests/effect-v4-document-symbols/package.json +++ b/testdata/tests/effect-v4-document-symbols/package.json @@ -2,7 +2,7 @@ "name": "effect-v4-document-symbols-tests", "private": true, "dependencies": { - "effect": "4.0.0-beta.104", + "effect": "4.0.0-beta.107", "@standard-schema/spec": "^1.1.0", "fast-check": "^4.4.0", "pure-rand": "^7.0.0", diff --git a/testdata/tests/effect-v4-refactors/package.json b/testdata/tests/effect-v4-refactors/package.json index a34f99cc..8a783a0e 100644 --- a/testdata/tests/effect-v4-refactors/package.json +++ b/testdata/tests/effect-v4-refactors/package.json @@ -2,7 +2,7 @@ "name": "effect-v4-refactors-tests", "private": true, "dependencies": { - "effect": "4.0.0-beta.104", + "effect": "4.0.0-beta.107", "@standard-schema/spec": "^1.1.0", "fast-check": "^4.4.0", "pure-rand": "^7.0.0", diff --git a/testdata/tests/effect-v4/package.json b/testdata/tests/effect-v4/package.json index f8cf2875..e4c47383 100644 --- a/testdata/tests/effect-v4/package.json +++ b/testdata/tests/effect-v4/package.json @@ -2,11 +2,11 @@ "name": "effect-v4-tests", "private": true, "dependencies": { - "@effect/vitest": "4.0.0-beta.104", + "@effect/vitest": "4.0.0-beta.107", "@standard-schema/spec": "^1.1.0", "@types/node": "^22.0.0", "@vitest/runner": "4.1.10", - "effect": "4.0.0-beta.104", + "effect": "4.0.0-beta.107", "fast-check": "^4.4.0", "pure-rand": "^7.0.0", "vitest": "4.1.10" diff --git a/testdata/tests/effect-v4/schemaMutableKey_ts2322.ts b/testdata/tests/effect-v4/schemaMutableKey_ts2322.ts index 22f713e4..6d40a4fb 100644 --- a/testdata/tests/effect-v4/schemaMutableKey_ts2322.ts +++ b/testdata/tests/effect-v4/schemaMutableKey_ts2322.ts @@ -1,4 +1,7 @@ -import { Schema, Struct as Struct_, SchemaAST } from "effect" +import { Schema, Struct as Struct_, SchemaAST } from "effect" +declare module "effect/SchemaAST" { + export function optionalKey
(ast: A): A +} interface optionalKeyLambda extends Struct_.Lambda { diff --git a/testdata/tests/effect-v4/schemaMutableKey_ts2322_pluginDisabled.ts b/testdata/tests/effect-v4/schemaMutableKey_ts2322_pluginDisabled.ts index 54e76534..bff487e2 100644 --- a/testdata/tests/effect-v4/schemaMutableKey_ts2322_pluginDisabled.ts +++ b/testdata/tests/effect-v4/schemaMutableKey_ts2322_pluginDisabled.ts @@ -9,7 +9,10 @@ } // @filename: schemaMutableKey_ts2322_pluginDisabled.ts -import { Schema, Struct as Struct_, SchemaAST } from "effect" +import { Schema, Struct as Struct_, SchemaAST } from "effect" +declare module "effect/SchemaAST" { + export function optionalKey(ast: A): A +} interface optionalKeyLambda extends Struct_.Lambda { diff --git a/testdata/tests/oxlint/package.json b/testdata/tests/oxlint/package.json index 699c606d..7cfaa681 100644 --- a/testdata/tests/oxlint/package.json +++ b/testdata/tests/oxlint/package.json @@ -2,6 +2,6 @@ "name": "oxlint-profile-tests", "private": true, "dependencies": { - "effect": "4.0.0-beta.104" + "effect": "4.0.0-beta.107" } } From 7417b66b9bbc9ab0e02538e41716c16ff4f3e1e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:51:43 +0000 Subject: [PATCH 20/23] chore: update generated/latest --- .changeset/refresh-patched-binaries.md | 5 ++ _packages/tsgo/src/patcher/discovery.ts | 19 +++-- _packages/tsgo/src/patcher/fileHash.ts | 17 ++++ _packages/tsgo/src/patcher/index.ts | 62 ++++++++++---- _packages/tsgo/src/patcher/types.ts | 1 + .../tsgo/test/experimental-oxlint.test.ts | 43 ++++++++-- _packages/tsgo/test/patcher.test.ts | 85 ++++++++++++++++--- 7 files changed, 186 insertions(+), 46 deletions(-) create mode 100644 .changeset/refresh-patched-binaries.md create mode 100644 _packages/tsgo/src/patcher/fileHash.ts diff --git a/.changeset/refresh-patched-binaries.md b/.changeset/refresh-patched-binaries.md new file mode 100644 index 00000000..5a6d598b --- /dev/null +++ b/.changeset/refresh-patched-binaries.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Refresh patched binaries when an updated `@effect/tsgo` package provides a different replacement artifact. diff --git a/_packages/tsgo/src/patcher/discovery.ts b/_packages/tsgo/src/patcher/discovery.ts index 5ca2db98..f7239b18 100644 --- a/_packages/tsgo/src/patcher/discovery.ts +++ b/_packages/tsgo/src/patcher/discovery.ts @@ -3,6 +3,7 @@ import * as Data from "effect/Data" import * as Effect from "effect/Effect" import * as FileSystem from "effect/FileSystem" import * as Path from "effect/Path" +import { hashFile } from "./fileHash.js" import type { Component, DiscoveredBinary } from "./types.js" export const defaultTypescriptPackageNames = ["typescript", "@typescript/native"] as const @@ -19,6 +20,8 @@ interface PackageMetadata { readonly main?: string } +type DiscoveredBinaryLocation = Omit + const isNativeTypescriptVersion = (version: string) => { const match = /\d+/.exec(version.trim()) return match !== null && Number(match[0]) >= 7 @@ -82,7 +85,7 @@ export const experimentalOxlintTarget = (platform: NodeJS.Platform, arch: string const discoverTypeScript: ( cwdRequire: NodeJS.Require, preferredPackage?: string -) => Effect.Effect = ( +) => Effect.Effect = ( cwdRequire, preferredPackage ) => Effect.gen(function*() { @@ -102,14 +105,14 @@ const discoverTypeScript: ( packageName: platformPackageName, packageVersion: platformPackage.version, binaryPath: path.join(path.dirname(platformPackage.packageJsonPath), "lib", binaryName) - } satisfies DiscoveredBinary] + } satisfies DiscoveredBinaryLocation] } return [] }) const discoverOxlint: ( cwdRequire: NodeJS.Require -) => Effect.Effect = (cwdRequire) => +) => Effect.Effect = (cwdRequire) => Effect.gen(function*() { const path = yield* Path.Path const oxlint = yield* optionally(readPackage(cwdRequire, "oxlint")) @@ -121,7 +124,7 @@ const discoverOxlint: ( ? error : new DiscoveryError({ reason: "Unable to determine the Oxlint platform target." }) }) - const discovered: Array = [] + const discovered: Array = [] if (oxlint !== undefined) { const binding = yield* readPackage(nodeModule.createRequire(oxlint.packageJsonPath), target.oxlintPackage) if (binding.main === undefined) { @@ -157,7 +160,7 @@ const discoverOxlint: ( const discoverVitePlusOxlint: ( cwdRequire: NodeJS.Require -) => Effect.Effect = (cwdRequire) => +) => Effect.Effect = (cwdRequire) => Effect.gen(function*() { const vitePlus = yield* optionally(readPackage(cwdRequire, "vite-plus")) if (vitePlus === undefined) return [] @@ -170,9 +173,13 @@ export const discoverBinaries = (cwd: string, preferredTypescriptPackage?: strin const typescript = yield* discoverTypeScript(cwdRequire, preferredTypescriptPackage) const oxlint = yield* discoverOxlint(cwdRequire) const vitePlusOxlint = yield* discoverVitePlusOxlint(cwdRequire) - return [...new Map( + const discovered = [...new Map( [...typescript, ...oxlint, ...vitePlusOxlint].map((binary) => [binary.binaryPath, binary]) ).values()] + return yield* Effect.forEach(discovered, (binary) => hashFile(binary.binaryPath).pipe( + Effect.map((fileHash) => ({ ...binary, fileHash })), + Effect.mapError(() => new DiscoveryError({ reason: `Unable to read discovered binary ${binary.binaryPath}.` })) + )) }) export const selectComponents = ( diff --git a/_packages/tsgo/src/patcher/fileHash.ts b/_packages/tsgo/src/patcher/fileHash.ts new file mode 100644 index 00000000..4918df89 --- /dev/null +++ b/_packages/tsgo/src/patcher/fileHash.ts @@ -0,0 +1,17 @@ +import * as Crypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as Encoding from "effect/Encoding" +import * as FileSystem from "effect/FileSystem" + +const textEncoder = new TextEncoder() + +export const hashBytes = (contents: string | Uint8Array) => Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const digest = yield* crypto.digest("SHA-256", typeof contents === "string" ? textEncoder.encode(contents) : contents) + return Encoding.encodeHex(digest) +}) + +export const hashFile = (filePath: string) => Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + return yield* hashBytes(yield* fs.readFile(filePath)) +}) diff --git a/_packages/tsgo/src/patcher/index.ts b/_packages/tsgo/src/patcher/index.ts index ad327671..ef97a372 100644 --- a/_packages/tsgo/src/patcher/index.ts +++ b/_packages/tsgo/src/patcher/index.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto" import * as nodeModule from "node:module" +import * as Crypto from "effect/Crypto" import * as Data from "effect/Data" import * as Effect from "effect/Effect" import * as FileSystem from "effect/FileSystem" @@ -7,6 +8,7 @@ import * as Path from "effect/Path" import * as Scope from "effect/Scope" import metadataJson from "../metadata.json" with { type: "json" } import { discoverBinaries, requireComponents, selectComponents } from "./discovery.js" +import { hashBytes, hashFile } from "./fileHash.js" import type { Component, DiscoveredBinary, @@ -40,6 +42,7 @@ const exists = (fs: FileSystem.FileSystem, filePath: string) => fs.exists(filePa export interface ResolvedReplacement { readonly path: string + readonly fileHash: string } export type ReplacementResolver = ( @@ -47,7 +50,7 @@ export type ReplacementResolver = ( ) => Effect.Effect< ResolvedReplacement, PatcherError | ReplacementUnavailableError, - FileSystem.FileSystem | Path.Path | Scope.Scope + Crypto.Crypto | FileSystem.FileSystem | Path.Path | Scope.Scope > const toKebabCase = (value: string): string => { @@ -96,9 +99,11 @@ export const renderOxlintDeclarations = (source: string): string => { const resolveOxlintDeclarations = (target: DiscoveredBinary) => Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const path = yield* Path.Path - const source = yield* fs.readFileString(target.binaryPath).pipe( + const backupPath = `${target.binaryPath}.original` + const sourcePath = (yield* exists(fs, backupPath)) ? backupPath : target.binaryPath + const source = yield* fs.readFileString(sourcePath).pipe( Effect.mapError((error) => new PatcherError({ - reason: `Unable to read Oxlint declarations at ${target.binaryPath}: ${error.message}` + reason: `Unable to read Oxlint declarations at ${sourcePath}: ${error.message}` })) ) const replacement = yield* Effect.try({ @@ -117,7 +122,12 @@ const resolveOxlintDeclarations = (target: DiscoveredBinary) => Effect.gen(funct reason: `Unable to write generated Oxlint declarations at ${replacementPath}: ${error.message}` })) ) - return { path: replacementPath } + const fileHash = yield* hashBytes(replacement).pipe( + Effect.mapError((error) => new PatcherError({ + reason: `Unable to hash generated Oxlint declarations: ${error.message}` + })) + ) + return { path: replacementPath, fileHash } }) const resolvePlatformPackage = (target: DiscoveredBinary) => Effect.gen(function*() { @@ -152,7 +162,12 @@ export const resolveReplacement: ReplacementResolver = (target) => Effect.gen(fu reason: `Missing packaged artifact ${replacementPath}.` }) } - return { path: replacementPath } + const fileHash = yield* hashFile(replacementPath).pipe( + Effect.mapError((error) => new PatcherError({ + reason: `Unable to hash packaged artifact ${replacementPath}: ${error.message}` + })) + ) + return { path: replacementPath, fileHash } }) export interface PreparePatchOptions { @@ -169,8 +184,13 @@ export const preparePatch = ( const fs = yield* FileSystem.FileSystem const resolver = options.resolveReplacement ?? resolveReplacement const operations: Array = [] + const cleanup: Array = [] const skipped: Array = [] - const available: Array<{ readonly target: DiscoveredBinary; readonly replacementPath: string }> = [] + const available: Array<{ + readonly target: DiscoveredBinary + readonly replacementPath: string + readonly backupExists: boolean + }> = [] for (const target of targets) { const backupPath = `${target.binaryPath}.original` @@ -179,14 +199,6 @@ export const preparePatch = ( if (!targetExists) { return yield* new PatcherError({ reason: `Installed binary does not exist: ${target.binaryPath}` }) } - if (backupExists) { - skipped.push({ - target, - reason: "already-patched", - message: `${target.component} skipped because backup already exists at ${backupPath}.` - }) - continue - } const replacement = yield* resolver(target).pipe( Effect.catchTag("ReplacementUnavailableError", (error) => { if (!options.skipMissing) return Effect.fail(error) @@ -195,12 +207,26 @@ export const preparePatch = ( }) ) if (replacement === undefined) continue - available.push({ target, replacementPath: replacement.path }) + if (backupExists && target.fileHash === replacement.fileHash) { + skipped.push({ + target, + reason: "already-patched", + message: `${target.component} skipped because its hash matches the replacement.` + }) + continue + } + available.push({ target, replacementPath: replacement.path, backupExists }) } - for (const { replacementPath, target } of available) { + for (const { backupExists, replacementPath, target } of available) { const backupPath = `${target.binaryPath}.original` - operations.push({ _tag: "Rename", sourcePath: target.binaryPath, destinationPath: backupPath }) + if (backupExists) { + const quarantinePath = `${target.binaryPath}.${randomUUID()}.patched` + operations.push({ _tag: "Rename", sourcePath: target.binaryPath, destinationPath: quarantinePath }) + cleanup.push({ _tag: "Remove", path: quarantinePath }) + } else { + operations.push({ _tag: "Rename", sourcePath: target.binaryPath, destinationPath: backupPath }) + } operations.push({ _tag: "Copy", sourcePath: replacementPath, @@ -211,7 +237,7 @@ export const preparePatch = ( } } - return { operations, cleanup: [], skipped } satisfies PreparedPatch + return { operations, cleanup, skipped } satisfies PreparedPatch }) export const prepareUnpatch = (targets: ReadonlyArray) => Effect.gen(function*() { diff --git a/_packages/tsgo/src/patcher/types.ts b/_packages/tsgo/src/patcher/types.ts index d98e03a4..b4d510f2 100644 --- a/_packages/tsgo/src/patcher/types.ts +++ b/_packages/tsgo/src/patcher/types.ts @@ -5,6 +5,7 @@ export interface DiscoveredBinary { readonly packageName: string readonly packageVersion: string readonly binaryPath: string + readonly fileHash: string } export type FileSystemOperation = RenameOperation | CopyOperation | ChmodOperation | RemoveOperation diff --git a/_packages/tsgo/test/experimental-oxlint.test.ts b/_packages/tsgo/test/experimental-oxlint.test.ts index 6483f45c..2d2dd95a 100644 --- a/_packages/tsgo/test/experimental-oxlint.test.ts +++ b/_packages/tsgo/test/experimental-oxlint.test.ts @@ -1,13 +1,16 @@ import * as NodeServices from "@effect/platform-node/NodeServices" import * as Effect from "effect/Effect" +import { createHash } from "node:crypto" import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises" import { tmpdir } from "node:os" -import { join } from "node:path" +import { dirname, join } from "node:path" import { afterEach, describe, expect, it } from "vitest" import { discoverBinaries, experimentalOxlintTarget } from "../src/patcher/index.js" const temporaryDirectories: Array = [] +const hash = (value: string) => createHash("sha256").update(value).digest("hex") + const makeTemporaryDirectory = async () => { const directory = await mkdtemp(join(tmpdir(), "effect-tsgo-oxlint-")) temporaryDirectories.push(directory) @@ -21,6 +24,11 @@ const writePackage = async (directory: string, packageName: string, packageJson: return packageDirectory } +const writeBinary = async (filePath: string, contents: string) => { + await mkdir(dirname(filePath), { recursive: true }) + await writeFile(filePath, contents) +} + afterEach(async () => { await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))) }) @@ -53,6 +61,11 @@ describe("experimental Oxlint discovery", () => { main: "oxlint.node" }) const tsgolintDirectory = await writePackage(directory, platform.tsgolintPackage, { version: "2.0.1" }) + await Promise.all([ + writeBinary(join(bindingDirectory, "oxlint.node"), "oxlint"), + writeBinary(join(oxlintDirectory, "dist", "index.d.ts"), "declarations"), + writeBinary(join(tsgolintDirectory, platform.tsgolintExecutable), "tsgolint") + ]) const discovered = await Effect.runPromise( discoverBinaries(directory).pipe(Effect.provide(NodeServices.layer)) @@ -62,19 +75,22 @@ describe("experimental Oxlint discovery", () => { component: "oxlint", packageName: platform.oxlintPackage, packageVersion: "1.0.1", - binaryPath: join(bindingDirectory, "oxlint.node") + binaryPath: join(bindingDirectory, "oxlint.node"), + fileHash: hash("oxlint") }, { component: "oxlint-dts", packageName: "oxlint", packageVersion: "1.0.0", - binaryPath: join(oxlintDirectory, "dist", "index.d.ts") + binaryPath: join(oxlintDirectory, "dist", "index.d.ts"), + fileHash: hash("declarations") }, { component: "oxlint-tsgolint", packageName: platform.tsgolintPackage, packageVersion: "2.0.1", - binaryPath: join(tsgolintDirectory, platform.tsgolintExecutable) + binaryPath: join(tsgolintDirectory, platform.tsgolintExecutable), + fileHash: hash("tsgolint") } ]) }) @@ -84,6 +100,8 @@ describe("experimental Oxlint discovery", () => { await writePackage(directory, "typescript", { version: "7.0.0" }) const platformPackage = `@typescript/typescript-${process.platform}-${process.arch}` const platformDirectory = await writePackage(directory, platformPackage, { version: "7.0.1" }) + const binaryPath = join(platformDirectory, "lib", process.platform === "win32" ? "tsc.exe" : "tsc") + await writeBinary(binaryPath, "typescript") const discovered = await Effect.runPromise( discoverBinaries(directory).pipe(Effect.provide(NodeServices.layer)) @@ -92,7 +110,8 @@ describe("experimental Oxlint discovery", () => { component: "typescript", packageName: platformPackage, packageVersion: "7.0.1", - binaryPath: join(platformDirectory, "lib", process.platform === "win32" ? "tsc.exe" : "tsc") + binaryPath, + fileHash: hash("typescript") }) }) @@ -111,6 +130,11 @@ describe("experimental Oxlint discovery", () => { platform.tsgolintPackage, { version: "2.0.1" } ) + await Promise.all([ + writeBinary(join(bindingDirectory, "oxlint.node"), "oxlint"), + writeBinary(join(oxlintDirectory, "dist", "index.d.ts"), "declarations"), + writeBinary(join(tsgolintDirectory, platform.tsgolintExecutable), "tsgolint") + ]) const discovered = await Effect.runPromise( discoverBinaries(directory).pipe(Effect.provide(NodeServices.layer)) @@ -120,19 +144,22 @@ describe("experimental Oxlint discovery", () => { component: "oxlint", packageName: platform.oxlintPackage, packageVersion: "1.0.1", - binaryPath: join(bindingDirectory, "oxlint.node") + binaryPath: join(bindingDirectory, "oxlint.node"), + fileHash: hash("oxlint") }, { component: "oxlint-dts", packageName: "oxlint", packageVersion: "1.0.0", - binaryPath: join(oxlintDirectory, "dist", "index.d.ts") + binaryPath: join(oxlintDirectory, "dist", "index.d.ts"), + fileHash: hash("declarations") }, { component: "oxlint-tsgolint", packageName: platform.tsgolintPackage, packageVersion: "2.0.1", - binaryPath: join(tsgolintDirectory, platform.tsgolintExecutable) + binaryPath: join(tsgolintDirectory, platform.tsgolintExecutable), + fileHash: hash("tsgolint") } ]) }) diff --git a/_packages/tsgo/test/patcher.test.ts b/_packages/tsgo/test/patcher.test.ts index e4fc7ecc..0a94c29a 100644 --- a/_packages/tsgo/test/patcher.test.ts +++ b/_packages/tsgo/test/patcher.test.ts @@ -1,8 +1,10 @@ import * as NodeServices from "@effect/platform-node/NodeServices" +import * as Crypto from "effect/Crypto" import * as Effect from "effect/Effect" import * as FileSystem from "effect/FileSystem" import * as Path from "effect/Path" import * as Scope from "effect/Scope" +import { createHash } from "node:crypto" import { access, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" @@ -20,13 +22,15 @@ import { const temporaryDirectories: Array = [] +const hash = (value: string) => createHash("sha256").update(value).digest("hex") + const makeTemporaryDirectory = async () => { const directory = await mkdtemp(join(tmpdir(), "effect-tsgo-patcher-")) temporaryDirectories.push(directory) return directory } -const run = (effect: Effect.Effect) => +const run = (effect: Effect.Effect) => Effect.runPromise(Effect.scoped(effect).pipe(Effect.provide(NodeServices.layer))) afterEach(async () => { @@ -47,14 +51,27 @@ describe("patcher", () => { writeFile(secondReplacement, "second-effect") ]) const targets: ReadonlyArray = [ - { component: "oxlint-dts", packageName: "oxlint", packageVersion: "1", binaryPath: first }, - { component: "oxlint-tsgolint", packageName: "oxlint-tsgolint", packageVersion: "2", binaryPath: second } + { + component: "oxlint-dts", + packageName: "oxlint", + packageVersion: "1", + binaryPath: first, + fileHash: hash("first") + }, + { + component: "oxlint-tsgolint", + packageName: "oxlint-tsgolint", + packageVersion: "2", + binaryPath: second, + fileHash: hash("second") + } ] const plan = await run(preparePatch(targets, { skipMissing: false, resolveReplacement: (target) => Effect.succeed({ - path: target.component === "oxlint-dts" ? firstReplacement : secondReplacement + path: target.component === "oxlint-dts" ? firstReplacement : secondReplacement, + fileHash: hash(target.component === "oxlint-dts" ? "first-effect" : "second-effect") }) })) expect(plan.operations).toEqual([ @@ -69,19 +86,21 @@ describe("patcher", () => { it("generates an Oxlint declaration replacement in the temporary directory", async () => { const directory = await makeTemporaryDirectory() const declarationPath = join(directory, "index.d.ts") - await writeFile(declarationPath, [ + const declarations = [ 'type LintPluginOptionsSchema = "eslint" | "typescript";', "type RuleNoConfig = unknown;", "interface DummyRuleMap {", ' "eslint/no-unused-vars"?: RuleNoConfig;', "}", "" - ].join("\n")) + ].join("\n") + await writeFile(declarationPath, declarations) const target: DiscoveredBinary = { component: "oxlint-dts", packageName: "oxlint", packageVersion: "1.0.0", - binaryPath: declarationPath + binaryPath: declarationPath, + fileHash: hash(await readFile(declarationPath, "utf8")) } const replacement = await run(Effect.gen(function*() { @@ -96,6 +115,15 @@ describe("patcher", () => { expect(replacement.source).toContain('"effecttsgo/floating-effect"?: RuleNoConfig;') expect(replacement.source).toContain('"eslint/no-unused-vars"?: RuleNoConfig;') await expect(access(replacement.path)).rejects.toThrow() + + await writeFile(`${declarationPath}.original`, declarations) + await writeFile(declarationPath, replacement.source) + const refreshed = await run(Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const resolved = yield* resolveReplacement(target) + return yield* fs.readFileString(resolved.path) + })) + expect(refreshed).toBe(replacement.source) }) it("rejects declarations whose expected anchors changed", () => { @@ -120,7 +148,7 @@ describe("patcher", () => { await expect(access(`${target}.original`)).rejects.toThrow() }) - it("treats the persisted original backup as already patched", async () => { + it("refreshes a stale patch while preserving the original backup", async () => { const directory = await makeTemporaryDirectory() const targetPath = join(directory, "binary") const replacementPath = join(directory, "replacement") @@ -131,18 +159,45 @@ describe("patcher", () => { component: "oxlint-tsgolint", packageName: "oxlint-tsgolint", packageVersion: "1", - binaryPath: targetPath + binaryPath: targetPath, + fileHash: hash("patched") + } + const plan = await run(preparePatch([target], { + skipMissing: false, + resolveReplacement: () => Effect.succeed({ path: replacementPath, fileHash: hash("replacement") }) + })) + expect(plan.operations.map(({ _tag }) => _tag)).toEqual(["Rename", "Copy", "Chmod"]) + expect(plan.cleanup).toHaveLength(1) + expect(plan.skipped).toEqual([]) + await run(applyPlan(plan)) + expect(await readFile(targetPath, "utf8")).toBe("replacement") + expect(await readFile(`${targetPath}.original`, "utf8")).toBe("original") + expect((await readdir(directory)).filter((name) => name.endsWith(".patched"))).toEqual([]) + }) + + it("skips a patch whose target already matches the replacement", async () => { + const directory = await makeTemporaryDirectory() + const targetPath = join(directory, "binary") + const replacementPath = join(directory, "replacement") + await writeFile(targetPath, "replacement") + await writeFile(`${targetPath}.original`, "original") + await writeFile(replacementPath, "replacement") + const target: DiscoveredBinary = { + component: "oxlint-tsgolint", + packageName: "oxlint-tsgolint", + packageVersion: "1", + binaryPath: targetPath, + fileHash: hash("replacement") } const plan = await run(preparePatch([target], { skipMissing: false, - resolveReplacement: () => Effect.succeed({ path: replacementPath }) + resolveReplacement: () => Effect.succeed({ path: replacementPath, fileHash: hash("replacement") }) })) expect(plan.operations).toEqual([]) expect(plan.skipped[0]?.reason).toBe("already-patched") expect(plan.skipped[0]?.message).toBe( - `oxlint-tsgolint skipped because backup already exists at ${targetPath}.original.` + `oxlint-tsgolint skipped because its hash matches the replacement.` ) - await expect(access(`${targetPath}.original.1`)).rejects.toThrow() }) it("rolls back prior reversible operations when mutation fails", async () => { @@ -170,7 +225,8 @@ describe("patcher", () => { component: "typescript", packageName: "typescript", packageVersion: "7.0.0", - binaryPath: targetPath + binaryPath: targetPath, + fileHash: hash("patched") } await writeFile(targetPath, "patched") await writeFile(`${targetPath}.original`, "original") @@ -190,7 +246,8 @@ describe("patcher", () => { component: "oxlint", packageName: "oxlint", packageVersion: "missing", - binaryPath: targetPath + binaryPath: targetPath, + fileHash: hash("original") } const unavailable = () => Effect.fail(new ReplacementUnavailableError({ target, reason: "not packaged" })) const plan = await run(preparePatch([target], { From 0e41adc095305043b18a80caf46bae28d1f11b6f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:41:07 +0000 Subject: [PATCH 21/23] chore: update generated/latest --- .changeset/refresh-patched-binaries.md | 5 ----- .changeset/update-effect-beta-107.md | 5 ----- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 7 +++++++ _packages/tsgo/package.json | 2 +- etscore/version_generated.go | 2 +- 19 files changed, 30 insertions(+), 19 deletions(-) delete mode 100644 .changeset/refresh-patched-binaries.md delete mode 100644 .changeset/update-effect-beta-107.md diff --git a/.changeset/refresh-patched-binaries.md b/.changeset/refresh-patched-binaries.md deleted file mode 100644 index 5a6d598b..00000000 --- a/.changeset/refresh-patched-binaries.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Refresh patched binaries when an updated `@effect/tsgo` package provides a different replacement artifact. diff --git a/.changeset/update-effect-beta-107.md b/.changeset/update-effect-beta-107.md deleted file mode 100644 index a9b0c149..00000000 --- a/.changeset/update-effect-beta-107.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Update Effect v4 dependencies and embedded test fixtures to `4.0.0-beta.107`. diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index dbf3f23a..e9339165 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index be057fbf..a749e152 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index 1da13cb6..7e53f7d2 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index d691f4d1..41b0e0d5 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index 945a7d64..502f11a4 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index d0618feb..7ff4844c 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index cc5093b8..5950cbc6 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index 0e4cf9df..516d1f2e 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index b31027ba..b7a11e21 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index 4d1cfe8f..ca83a6ec 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index 29cf1f58..0269f51d 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index 8bb16cf0..c9b01afd 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 101e89b7..43a39b1b 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.36.3 + ## 0.36.2 ## 0.36.1 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index 7ff0bfaa..8e9d5106 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.36.2", + "version": "0.36.3", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index f2d74841..37990a6a 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/tsgo +## 0.36.3 + +### Patch Changes + +- 73f7f28: Refresh patched binaries when an updated `@effect/tsgo` package provides a different replacement artifact. +- 9190801: Update Effect v4 dependencies and embedded test fixtures to `4.0.0-beta.107`. + ## 0.36.2 ### Patch Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 5866146c..4199a946 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.36.2", + "version": "0.36.3", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/etscore/version_generated.go b/etscore/version_generated.go index 31bde5d8..0ae8e1b1 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.36.2" +const EffectVersion = "0.36.3" From c88d7ebbe915b22e1d291b1153d0e6a833d1654b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:18:46 +0000 Subject: [PATCH 22/23] chore: update generated/latest --- .changeset/fix-no-emit-ignored-diagnostics.md | 5 ++ .../typescript-go/009-execute-tsc-emit.patch | 30 ++++++++++ etsexecutehooks/doc.go | 22 +++---- etsexecutehooks/init.go | 2 + etsexecutehooks/init_test.go | 57 +++++++++++++++++++ shim/compiler/shim.go | 3 + 6 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 .changeset/fix-no-emit-ignored-diagnostics.md diff --git a/.changeset/fix-no-emit-ignored-diagnostics.md b/.changeset/fix-no-emit-ignored-diagnostics.md new file mode 100644 index 00000000..ab63d2df --- /dev/null +++ b/.changeset/fix-no-emit-ignored-diagnostics.md @@ -0,0 +1,5 @@ +--- +"@effect/tsgo": patch +--- + +Allow declaration emit with `noEmitOnError` when all diagnostics are ignored Effect diagnostics. diff --git a/_patches/typescript-go/009-execute-tsc-emit.patch b/_patches/typescript-go/009-execute-tsc-emit.patch index fc7df1a2..913f69ab 100644 --- a/_patches/typescript-go/009-execute-tsc-emit.patch +++ b/_patches/typescript-go/009-execute-tsc-emit.patch @@ -67,3 +67,33 @@ index 3a1e3de69..8cb7c24c0 100644 result.Status = ExitStatusDiagnosticsPresent_OutputsGenerated } return result, statistics +diff --git a/internal/compiler/program.go b/internal/compiler/program.go +--- a/internal/compiler/program.go ++++ b/internal/compiler/program.go +@@ -30,6 +30,16 @@ import ( + "github.com/microsoft/typescript-go/internal/tracing" + "github.com/microsoft/typescript-go/internal/tsoptions" + "github.com/microsoft/typescript-go/internal/tspath" + ) + ++// FilterDiagnosticsForNoEmitOnErrorCallback is invoked before diagnostics ++// suppress emit when noEmitOnError is enabled. ++var FilterDiagnosticsForNoEmitOnErrorCallback func(*core.CompilerOptions, []*ast.Diagnostic) []*ast.Diagnostic ++ ++// RegisterFilterDiagnosticsForNoEmitOnErrorCallback registers a callback to ++// filter diagnostics used by noEmitOnError. ++func RegisterFilterDiagnosticsForNoEmitOnErrorCallback(cb func(*core.CompilerOptions, []*ast.Diagnostic) []*ast.Diagnostic) { ++ FilterDiagnosticsForNoEmitOnErrorCallback = cb ++} ++ + type ProgramOptions struct { +@@ -1743,6 +1753,9 @@ func HandleNoEmitOnError(ctx context.Context, program ProgramLike, file *ast.Sou + program.GetBindDiagnostics, + program.GetSemanticDiagnostics, + ) ++ if FilterDiagnosticsForNoEmitOnErrorCallback != nil { ++ diagnostics = FilterDiagnosticsForNoEmitOnErrorCallback(program.Options(), diagnostics) ++ } + if len(diagnostics) == 0 { + return nil // No diagnostics, so we can proceed with emitting + } diff --git a/etsexecutehooks/doc.go b/etsexecutehooks/doc.go index 57eacfa0..6e4ed1f2 100644 --- a/etsexecutehooks/doc.go +++ b/etsexecutehooks/doc.go @@ -1,11 +1,11 @@ -// Package etsexecutehooks provides exit-code filtering for Effect diagnostics. -// -// This package registers a FilterDiagnosticsForExitCodeCallback that filters out -// Effect diagnostics from exit-code determination based on the -// IgnoreEffectSuggestionsInTscExitCode and IgnoreEffectWarningsInTscExitCode -// configuration options. -// -// Import this package with a blank import in the main entry point: -// -// import _ "github.com/effect-ts/tsgo/etsexecutehooks" -package etsexecutehooks +// Package etsexecutehooks provides command-line filtering for Effect diagnostics. +// +// This package registers a FilterDiagnosticsForExitCodeCallback that filters out +// Effect diagnostics from exit-code and noEmitOnError determination based on the +// IgnoreEffectSuggestionsInTscExitCode and IgnoreEffectWarningsInTscExitCode +// configuration options. +// +// Import this package with a blank import in the main entry point: +// +// import _ "github.com/effect-ts/tsgo/etsexecutehooks" +package etsexecutehooks diff --git a/etsexecutehooks/init.go b/etsexecutehooks/init.go index a98320ba..f3b663a9 100644 --- a/etsexecutehooks/init.go +++ b/etsexecutehooks/init.go @@ -4,6 +4,7 @@ import ( "github.com/effect-ts/tsgo/etscore" "github.com/effect-ts/tsgo/internal/rule" "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/compiler" "github.com/microsoft/typescript-go/shim/core" "github.com/microsoft/typescript-go/shim/diagnostics" "github.com/microsoft/typescript-go/shim/execute/tsc" @@ -11,6 +12,7 @@ import ( func init() { tsc.RegisterFilterDiagnosticsForExitCodeCallback(filterDiagnosticsForExitCode) + compiler.RegisterFilterDiagnosticsForNoEmitOnErrorCallback(filterDiagnosticsForExitCode) } // filterDiagnosticsForExitCode is the callback registered with the tsc package. diff --git a/etsexecutehooks/init_test.go b/etsexecutehooks/init_test.go index f41a2c70..2d44e877 100644 --- a/etsexecutehooks/init_test.go +++ b/etsexecutehooks/init_test.go @@ -1,14 +1,48 @@ package etsexecutehooks import ( + "context" "testing" "github.com/effect-ts/tsgo/etscore" "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/compiler" "github.com/microsoft/typescript-go/shim/core" "github.com/microsoft/typescript-go/shim/diagnostics" + "github.com/microsoft/typescript-go/shim/tspath" ) +type noEmitProgram struct { + opts *core.CompilerOptions + diagnostics []*ast.Diagnostic +} + +func (p *noEmitProgram) Options() *core.CompilerOptions { return p.opts } +func (p *noEmitProgram) GetSourceFile(string) *ast.SourceFile { return nil } +func (p *noEmitProgram) GetSourceFiles() []*ast.SourceFile { return nil } +func (p *noEmitProgram) GetConfigFileParsingDiagnostics() []*ast.Diagnostic { return p.diagnostics } +func (p *noEmitProgram) GetSyntacticDiagnostics(context.Context, *ast.SourceFile) []*ast.Diagnostic { + return nil +} +func (p *noEmitProgram) GetBindDiagnostics(context.Context, *ast.SourceFile) []*ast.Diagnostic { + return nil +} +func (p *noEmitProgram) GetProgramDiagnostics() []*ast.Diagnostic { return nil } +func (p *noEmitProgram) GetGlobalDiagnostics(context.Context) []*ast.Diagnostic { return nil } +func (p *noEmitProgram) GetSemanticDiagnostics(context.Context, *ast.SourceFile) []*ast.Diagnostic { + return nil +} +func (p *noEmitProgram) GetDeclarationDiagnostics(context.Context, *ast.SourceFile) []*ast.Diagnostic { + return nil +} +func (p *noEmitProgram) GetSuggestionDiagnostics(context.Context, *ast.SourceFile) []*ast.Diagnostic { + return nil +} +func (p *noEmitProgram) Emit(context.Context, compiler.EmitOptions) *compiler.EmitResult { return nil } +func (p *noEmitProgram) CommonSourceDirectory() string { return "" } +func (p *noEmitProgram) IsSourceFileDefaultLibrary(tspath.Path) bool { return false } +func (p *noEmitProgram) Program() *compiler.Program { return nil } + // makeDiag creates a minimal diagnostic with the given code and category. func makeDiag(code int32, category diagnostics.Category) *ast.Diagnostic { return ast.NewDiagnosticFromSerialized( @@ -112,6 +146,29 @@ func TestFilterDiagnosticsForExitCode_IgnoreWarnings(t *testing.T) { } } +func TestNoEmitOnError_IgnoresConfiguredEffectWarnings(t *testing.T) { + t.Parallel() + opts := &core.CompilerOptions{ + NoEmitOnError: core.BoolToTristate(true), + Effect: &etscore.EffectPluginOptions{ + IgnoreEffectWarningsInTscExitCode: true, + }, + } + program := &noEmitProgram{ + opts: opts, + diagnostics: []*ast.Diagnostic{makeDiag(377011, diagnostics.CategoryWarning)}, + } + + if result := compiler.HandleNoEmitOnError(context.Background(), program, nil); result != nil { + t.Fatal("expected ignored Effect warning not to suppress emit") + } + + program.diagnostics = []*ast.Diagnostic{makeDiag(1002, diagnostics.CategoryError)} + if result := compiler.HandleNoEmitOnError(context.Background(), program, nil); result == nil || !result.EmitSkipped { + t.Fatal("expected TypeScript error to suppress emit") + } +} + func TestFilterDiagnosticsForExitCode_ErrorsNotFilteredByDefault(t *testing.T) { t.Parallel() // With suggestion+warning ignore set but error ignore NOT set, errors are kept diff --git a/shim/compiler/shim.go b/shim/compiler/shim.go index 69f3a563..be981f02 100644 --- a/shim/compiler/shim.go +++ b/shim/compiler/shim.go @@ -26,6 +26,7 @@ const EmitOnlyJs = compiler.EmitOnlyJs type EmitOptions = compiler.EmitOptions type EmitResult = compiler.EmitResult type FileIncludeReason = compiler.FileIncludeReason +var FilterDiagnosticsForNoEmitOnErrorCallback = compiler.FilterDiagnosticsForNoEmitOnErrorCallback //go:linkname FilterNoEmitSemanticDiagnostics github.com/microsoft/typescript-go/internal/compiler.FilterNoEmitSemanticDiagnostics func FilterNoEmitSemanticDiagnostics(diagnostics []*ast.Diagnostic, options *core.CompilerOptions) []*ast.Diagnostic //go:linkname GetDiagnosticsOfAnyProgram github.com/microsoft/typescript-go/internal/compiler.GetDiagnosticsOfAnyProgram @@ -42,6 +43,8 @@ func NewProgram(opts compiler.ProgramOptions) *compiler.Program type Program = compiler.Program type ProgramLike = compiler.ProgramLike type ProgramOptions = compiler.ProgramOptions +//go:linkname RegisterFilterDiagnosticsForNoEmitOnErrorCallback github.com/microsoft/typescript-go/internal/compiler.RegisterFilterDiagnosticsForNoEmitOnErrorCallback +func RegisterFilterDiagnosticsForNoEmitOnErrorCallback(cb func(*core.CompilerOptions, []*ast.Diagnostic) []*ast.Diagnostic) //go:linkname SortAndDeduplicateDiagnostics github.com/microsoft/typescript-go/internal/compiler.SortAndDeduplicateDiagnostics func SortAndDeduplicateDiagnostics(diagnostics []*ast.Diagnostic) []*ast.Diagnostic type SourceFileMayBeEmittedHost = compiler.SourceFileMayBeEmittedHost From 7d9e9fbe1e34e52c67228f4a37c679ea41fe6ec8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:44:48 +0000 Subject: [PATCH 23/23] chore: update generated/latest --- .changeset/fix-no-emit-ignored-diagnostics.md | 5 ----- _packages/tsgo-darwin-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-arm64/package.json | 2 +- _packages/tsgo-darwin-x64/CHANGELOG.md | 2 ++ _packages/tsgo-darwin-x64/package.json | 2 +- _packages/tsgo-linux-arm/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm/package.json | 2 +- _packages/tsgo-linux-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-arm64/package.json | 2 +- _packages/tsgo-linux-x64/CHANGELOG.md | 2 ++ _packages/tsgo-linux-x64/package.json | 2 +- _packages/tsgo-win32-arm64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-arm64/package.json | 2 +- _packages/tsgo-win32-x64/CHANGELOG.md | 2 ++ _packages/tsgo-win32-x64/package.json | 2 +- _packages/tsgo/CHANGELOG.md | 6 ++++++ _packages/tsgo/package.json | 2 +- etscore/version_generated.go | 2 +- 18 files changed, 29 insertions(+), 14 deletions(-) delete mode 100644 .changeset/fix-no-emit-ignored-diagnostics.md diff --git a/.changeset/fix-no-emit-ignored-diagnostics.md b/.changeset/fix-no-emit-ignored-diagnostics.md deleted file mode 100644 index ab63d2df..00000000 --- a/.changeset/fix-no-emit-ignored-diagnostics.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@effect/tsgo": patch ---- - -Allow declaration emit with `noEmitOnError` when all diagnostics are ignored Effect diagnostics. diff --git a/_packages/tsgo-darwin-arm64/CHANGELOG.md b/_packages/tsgo-darwin-arm64/CHANGELOG.md index e9339165..d043b398 100644 --- a/_packages/tsgo-darwin-arm64/CHANGELOG.md +++ b/_packages/tsgo-darwin-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-arm64 +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-darwin-arm64/package.json b/_packages/tsgo-darwin-arm64/package.json index a749e152..2394146b 100644 --- a/_packages/tsgo-darwin-arm64/package.json +++ b/_packages/tsgo-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-arm64", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-darwin-x64/CHANGELOG.md b/_packages/tsgo-darwin-x64/CHANGELOG.md index 7e53f7d2..760cca23 100644 --- a/_packages/tsgo-darwin-x64/CHANGELOG.md +++ b/_packages/tsgo-darwin-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-darwin-x64 +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-darwin-x64/package.json b/_packages/tsgo-darwin-x64/package.json index 41b0e0d5..a5b6c9e1 100644 --- a/_packages/tsgo-darwin-x64/package.json +++ b/_packages/tsgo-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-darwin-x64", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm/CHANGELOG.md b/_packages/tsgo-linux-arm/CHANGELOG.md index 502f11a4..7f21b730 100644 --- a/_packages/tsgo-linux-arm/CHANGELOG.md +++ b/_packages/tsgo-linux-arm/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-linux-arm/package.json b/_packages/tsgo-linux-arm/package.json index 7ff4844c..977f9886 100644 --- a/_packages/tsgo-linux-arm/package.json +++ b/_packages/tsgo-linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-arm64/CHANGELOG.md b/_packages/tsgo-linux-arm64/CHANGELOG.md index 5950cbc6..ad7b51ff 100644 --- a/_packages/tsgo-linux-arm64/CHANGELOG.md +++ b/_packages/tsgo-linux-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-arm64 +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-linux-arm64/package.json b/_packages/tsgo-linux-arm64/package.json index 516d1f2e..38ddca9c 100644 --- a/_packages/tsgo-linux-arm64/package.json +++ b/_packages/tsgo-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-arm64", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-linux-x64/CHANGELOG.md b/_packages/tsgo-linux-x64/CHANGELOG.md index b7a11e21..276d50d0 100644 --- a/_packages/tsgo-linux-x64/CHANGELOG.md +++ b/_packages/tsgo-linux-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-linux-x64 +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-linux-x64/package.json b/_packages/tsgo-linux-x64/package.json index ca83a6ec..478b0eb7 100644 --- a/_packages/tsgo-linux-x64/package.json +++ b/_packages/tsgo-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-linux-x64", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-arm64/CHANGELOG.md b/_packages/tsgo-win32-arm64/CHANGELOG.md index 0269f51d..9db3546e 100644 --- a/_packages/tsgo-win32-arm64/CHANGELOG.md +++ b/_packages/tsgo-win32-arm64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-arm64 +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-win32-arm64/package.json b/_packages/tsgo-win32-arm64/package.json index c9b01afd..3f112fe1 100644 --- a/_packages/tsgo-win32-arm64/package.json +++ b/_packages/tsgo-win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-arm64", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo-win32-x64/CHANGELOG.md b/_packages/tsgo-win32-x64/CHANGELOG.md index 43a39b1b..90d54829 100644 --- a/_packages/tsgo-win32-x64/CHANGELOG.md +++ b/_packages/tsgo-win32-x64/CHANGELOG.md @@ -1,5 +1,7 @@ # @effect/tsgo-win32-x64 +## 0.36.4 + ## 0.36.3 ## 0.36.2 diff --git a/_packages/tsgo-win32-x64/package.json b/_packages/tsgo-win32-x64/package.json index 8e9d5106..83f2ebaf 100644 --- a/_packages/tsgo-win32-x64/package.json +++ b/_packages/tsgo-win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo-win32-x64", - "version": "0.36.3", + "version": "0.36.4", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", "repository": { diff --git a/_packages/tsgo/CHANGELOG.md b/_packages/tsgo/CHANGELOG.md index 37990a6a..71487b0b 100644 --- a/_packages/tsgo/CHANGELOG.md +++ b/_packages/tsgo/CHANGELOG.md @@ -1,5 +1,11 @@ # @effect/tsgo +## 0.36.4 + +### Patch Changes + +- 7a616ec: Allow declaration emit with `noEmitOnError` when all diagnostics are ignored Effect diagnostics. + ## 0.36.3 ### Patch Changes diff --git a/_packages/tsgo/package.json b/_packages/tsgo/package.json index 4199a946..74ed4bec 100644 --- a/_packages/tsgo/package.json +++ b/_packages/tsgo/package.json @@ -1,6 +1,6 @@ { "name": "@effect/tsgo", - "version": "0.36.3", + "version": "0.36.4", "type": "module", "description": "Effect Language Service for TypeScript-Go — Effect-specific diagnostics and hover features.", "license": "MIT", diff --git a/etscore/version_generated.go b/etscore/version_generated.go index 0ae8e1b1..9d4ed496 100644 --- a/etscore/version_generated.go +++ b/etscore/version_generated.go @@ -2,4 +2,4 @@ package etscore -const EffectVersion = "0.36.3" +const EffectVersion = "0.36.4"