Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-tools-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/tsgo": minor
---

Add an `effect-tsgo diagnostics` command for reporting Effect diagnostics from a file or TypeScript project. The command supports pretty, text, JSON, and GitHub Actions output, severity filtering, progress reporting, strict warning handling, and inline language-service configuration.
5 changes: 5 additions & 0 deletions .changeset/fix-layer-magic-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/tsgo": patch
---

Start layer magic compositions from `Layer.empty` when the first layer does not provide a requested output service.
5 changes: 5 additions & 0 deletions .changeset/setup-vscode-settings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/tsgo": patch
---

Update `effect-tsgo setup` to configure the current TypeScript Go VS Code settings, including the workspace TypeScript SDK path and additional SDK locations.
2 changes: 1 addition & 1 deletion .changeset/update-typescript-go.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@effect/tsgo": patch
---

Update to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260712.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/168e7015edf98244febc8f4ae450b673b5d195d7) commit `168e7015edf98244febc8f4ae450b673b5d195d7`.
Update to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260713.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/168e7015edf98244febc8f4ae450b673b5d195d7) commit `168e7015edf98244febc8f4ae450b673b5d195d7`.
61 changes: 60 additions & 1 deletion _packages/tsgo/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import * as Schema from "effect/Schema"
import * as Command from "effect/unstable/cli/Command"
import * as Flag from "effect/unstable/cli/Flag"
import { configCommand } from "./config.js"
import {
type DiagnosticsOutputFormat,
propagateDiagnosticsExit,
runDiagnosticsBinary
} from "./diagnostics.js"
import { setupCommand } from "./setup/index.js"
import { defaultTypescriptPackageNames, isNativeTypescriptVersion } from "./setup/consts.js"
import * as pkgJson from "../package.json" with { type: "json" }
Expand Down Expand Up @@ -558,8 +563,62 @@ const getExePathCommand = Command.make("get-exe-path").pipe(
)
)

const diagnosticsCommand = Command.make("diagnostics", {
file: Flag.file("file").pipe(
Flag.optional,
Flag.withDescription("The full path of the file to check for diagnostics")
),
project: Flag.file("project").pipe(
Flag.optional,
Flag.withDescription("The full path of the project tsconfig.json file to check for diagnostics")
),
format: Flag.choice(
"format",
["json", "pretty", "text", "github-actions"] as ReadonlyArray<DiagnosticsOutputFormat>
).pipe(
Flag.withDefault("pretty" as const),
Flag.withDescription(
"Output format: json (machine-readable), pretty (colored with context), text (plain text), github-actions (workflow commands)"
)
),
strict: Flag.boolean("strict").pipe(
Flag.withDefault(false),
Flag.withDescription("Treat warnings as errors (affects exit code)")
),
severity: Flag.string("severity").pipe(
Flag.optional,
Flag.withDescription("Filter by severity levels (comma-separated: error,warning,message)")
),
progress: Flag.boolean("progress").pipe(
Flag.withDefault(false),
Flag.withDescription("Show progress as files are checked (outputs to stderr)")
),
lspconfig: Flag.string("lspconfig").pipe(
Flag.optional,
Flag.withDescription("An optional inline JSON lsp config that replaces the current project lsp config")
)
}).pipe(
Command.withDescription("Gets the Effect language service diagnostics on the given files or project"),
Command.withHandler(({ file, format, lspconfig, progress, project, severity, strict }) =>
Effect.gen(function*() {
const installedTypeScript = yield* resolveInstalledTypeScriptBinary(defaultTypescriptPackageNames)
const binaryPath = yield* getPackagedBinaryPath(installedTypeScript, false)
const result = runDiagnosticsBinary(binaryPath, {
cwd: process.cwd(),
file: Option.getOrUndefined(file),
project: Option.getOrUndefined(project),
format,
strict,
severity: Option.getOrUndefined(severity),
progress,
lspconfig: Option.getOrUndefined(lspconfig)
})
propagateDiagnosticsExit(result)
}))
)

const rootCommand = Command.make("tsgo").pipe(
Command.withSubcommands([patchCommand, unpatchCommand, getExePathCommand, setupCommand, configCommand])
Command.withSubcommands([patchCommand, unpatchCommand, getExePathCommand, diagnosticsCommand, setupCommand, configCommand])
)


Expand Down
55 changes: 55 additions & 0 deletions _packages/tsgo/src/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as childProcess from "node:child_process"

export type DiagnosticsOutputFormat = "json" | "pretty" | "text" | "github-actions"

export interface DiagnosticsRequest {
readonly cwd: string
readonly file?: string
readonly project?: string
readonly format: DiagnosticsOutputFormat
readonly strict: boolean
readonly severity?: string
readonly progress: boolean
readonly lspconfig?: string
}

export interface DiagnosticsProcessResult {
readonly status: number | null
readonly signal: NodeJS.Signals | null
readonly error?: Error
}

export type SpawnDiagnosticsProcess = (
binaryPath: string,
argv: ReadonlyArray<string>,
options: { readonly stdio: "inherit" }
) => DiagnosticsProcessResult

export interface DiagnosticsParentProcess {
readonly pid: number
exitCode: number | string | null | undefined
kill(pid: number, signal: NodeJS.Signals): boolean
}

export const runDiagnosticsBinary = (
binaryPath: string,
request: DiagnosticsRequest,
spawn: SpawnDiagnosticsProcess = childProcess.spawnSync
): DiagnosticsProcessResult => {
const result = spawn(binaryPath, ["--effect-cli-diagnostics", JSON.stringify(request)], { stdio: "inherit" })
if (result.error !== undefined) {
throw result.error
}
return result
}

export const propagateDiagnosticsExit = (
result: DiagnosticsProcessResult,
parent: DiagnosticsParentProcess = process
): void => {
if (result.signal !== null) {
parent.kill(parent.pid, result.signal)
return
}
parent.exitCode = result.status ?? 1
}
39 changes: 20 additions & 19 deletions _packages/tsgo/src/setup/changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,9 +742,18 @@ const computeVSCodeSettingsChanges = (
return emptyFileChangesResult()
}

const ctx = createTrackerContext()

const fileChanges = tsInternal.textChanges.ChangeTracker.with(
const ctx = createTrackerContext()

const createSettingValue = (value: unknown): ts.Expression =>
typeof value === "string"
? ts.factory.createStringLiteral(value)
: typeof value === "boolean"
? value ? ts.factory.createTrue() : ts.factory.createFalse()
: Array.isArray(value) && value.every((item): item is string => typeof item === "string")
? ts.factory.createArrayLiteralExpression(value.map((item) => ts.factory.createStringLiteral(item)))
: ts.factory.createNull()

const fileChanges = tsInternal.textChanges.ChangeTracker.with(
ctx,
(tracker: any) => {
if (rootObj.properties.length === 0) {
Expand All @@ -754,14 +763,10 @@ const computeVSCodeSettingsChanges = (
for (const [key, value] of Object.entries(target.settings)) {
descriptions.push(`Add ${key} setting`)
newProperties.push(
ts.factory.createPropertyAssignment(
ts.factory.createStringLiteral(key),
typeof value === "string"
? ts.factory.createStringLiteral(value)
: typeof value === "boolean"
? value ? ts.factory.createTrue() : ts.factory.createFalse()
: ts.factory.createNull()
)
ts.factory.createPropertyAssignment(
ts.factory.createStringLiteral(key),
createSettingValue(value)
)
)
}

Expand All @@ -775,14 +780,10 @@ const computeVSCodeSettingsChanges = (
if (!existingProp) {
descriptions.push(`Add ${key} setting`)

const newProp = ts.factory.createPropertyAssignment(
ts.factory.createStringLiteral(key),
typeof value === "string"
? ts.factory.createStringLiteral(value)
: typeof value === "boolean"
? value ? ts.factory.createTrue() : ts.factory.createFalse()
: ts.factory.createNull()
)
const newProp = ts.factory.createPropertyAssignment(
ts.factory.createStringLiteral(key),
createSettingValue(value)
)
insertNodeAtEndOfList(tracker, current.sourceFile, rootObj.properties, newProp)
}
}
Expand Down
6 changes: 0 additions & 6 deletions _packages/tsgo/src/setup/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,3 @@ export const isNativeTypescriptVersion = (version: string): boolean => {
const match = /\d+/.exec(version.trim())
return match !== null && Number(match[0]) >= 7
}

/**
* Resolve the VS Code TypeScript 7 tsdk folder.
*/
export const nativeBackendTsdkPath = (packageName: string): string =>
"node_modules/" + packageName
10 changes: 5 additions & 5 deletions _packages/tsgo/src/setup/target-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Option from "effect/Option"
import type * as Terminal from "effect/Terminal"
import * as Prompt from "effect/unstable/cli/Prompt"
import { applyPresetDiagnosticSeverities, type DiagnosticPresetName, isPresetEnabled } from "../presets.js"
import { defaultTypescriptPackageNames, nativeBackendTsdkPath } from "./consts.js"
import { defaultTypescriptPackageNames } from "./consts.js"
import type { Assessment } from "./types.js"
import type { Editor, Target } from "./target.js"
import { getAllPresets, getAllRules } from "./rule-info.js"
Expand Down Expand Up @@ -134,14 +134,14 @@ export const gatherTargetState = (
})

// Build target state
// Point the TypeScript 7 extension at the project TypeScript package.
const defaultTypescriptPackageName = defaultTypescriptPackageNames[0]
const vscodeSettings: Option.Option<Target.VSCodeSettings> = editors.includes("vscode")
? Option.some({
settings: {
"typescript.native-preview.tsdk": nativeBackendTsdkPath(defaultTypescriptPackageName),
"typescript.experimental.useTsgo": true,
"js/ts.experimental.useTsgo": true
"js/ts.experimental.useTsgo": true,
"js/ts.tsdk.path": "./node_modules/typescript/bin",
"js/ts.tsdk.promptToUseWorkspaceVersion": true,
"js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"]
}
})
: Option.none()
Expand Down
27 changes: 16 additions & 11 deletions _packages/tsgo/test/setup/__snapshots__/setup-cli.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ exports[`Setup CLI > should add LSP plugin alongside existing plugins > tsconfig

exports[`Setup CLI > should add LSP with VS Code editor selected and create new settings file > .vscode/settings.json 1`] = `
"{
"typescript.native-preview.tsdk": "node_modules/typescript",
"typescript.experimental.useTsgo": true,
"js/ts.experimental.useTsgo": true
"js/ts.experimental.useTsgo": true,
"js/ts.tsdk.path": "./node_modules/typescript/bin",
"js/ts.tsdk.promptToUseWorkspaceVersion": true,
"js/ts.tsdk.additionalLocations": [
"./node_modules/typescript/bin"
]
}
"
`;
Expand Down Expand Up @@ -511,9 +514,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.experimental.useTsgo": true,
"typescript.experimental.useTsgo": true,
"typescript.native-preview.tsdk": "node_modules/typescript"
"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
}"
`;

Expand All @@ -528,7 +532,7 @@ exports[`Setup CLI > should preserve all existing VS Code settings from a real r
"file": "tsconfig.json",
},
{
"description": "Add typescript.native-preview.tsdk setting; Add typescript.experimental.useTsgo setting; Add js/ts.experimental.useTsgo setting",
"description": "Add js/ts.experimental.useTsgo setting; Add js/ts.tsdk.path setting; Add js/ts.tsdk.promptToUseWorkspaceVersion setting; Add js/ts.tsdk.additionalLocations setting",
"file": ".vscode/settings.json",
},
]
Expand Down Expand Up @@ -575,9 +579,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.experimental.useTsgo": true,
"typescript.experimental.useTsgo": true,
"typescript.native-preview.tsdk": "node_modules/typescript"
"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
}"
`;

Expand All @@ -592,7 +597,7 @@ exports[`Setup CLI > should preserve existing VS Code settings when adding LSP-s
"file": "tsconfig.json",
},
{
"description": "Add typescript.native-preview.tsdk setting; Add typescript.experimental.useTsgo setting; Add js/ts.experimental.useTsgo setting",
"description": "Add js/ts.experimental.useTsgo setting; Add js/ts.tsdk.path setting; Add js/ts.tsdk.promptToUseWorkspaceVersion setting; Add js/ts.tsdk.additionalLocations setting",
"file": ".vscode/settings.json",
},
]
Expand Down
15 changes: 15 additions & 0 deletions _packages/tsgo/test/setup/changes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,21 @@ describe("computeChanges", () => {
}
}
})

it("should serialize array settings when modifying existing vscode settings", () => {
const result = runComputeChanges({
vscodeSettingsText: JSON.stringify({ "editor.formatOnSave": true }, null, 2),
vscodeTargetSettings: {
"js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"]
}
})

const vscodeChange = result.codeActions
.flatMap((action) => action.changes)
.find((change) => change.fileName.includes("settings.json"))

expect(vscodeChange?.textChanges[0]?.newText).toContain('["./node_modules/typescript/bin"]')
})
})

describe("new-file code action for .vscode/settings.json", () => {
Expand Down
9 changes: 1 addition & 8 deletions _packages/tsgo/test/setup/consts.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { describe, it, expect } from "vitest"
import {
defaultTypescriptPackageNames,
isNativeTypescriptVersion,
nativeBackendTsdkPath
isNativeTypescriptVersion
} from "../../src/setup/consts.js"

describe("isNativeTypescriptVersion", () => {
Expand All @@ -29,12 +28,6 @@ describe("isNativeTypescriptVersion", () => {
})
})

describe("nativeBackendTsdkPath", () => {
it("returns the node_modules folder for TypeScript", () => {
expect(nativeBackendTsdkPath(defaultTypescriptPackageNames[0])).toBe("node_modules/typescript")
})
})

describe("defaultTypescriptPackageNames", () => {
it("tries typescript before the @typescript/native alias", () => {
expect(defaultTypescriptPackageNames).toEqual(["typescript", "@typescript/native"])
Expand Down
7 changes: 4 additions & 3 deletions _packages/tsgo/test/setup/setup-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ function expectSetupChanges(

const VSCODE_SETTINGS: Target.VSCodeSettings = {
settings: {
"typescript.native-preview.tsdk": "node_modules/typescript",
"typescript.experimental.useTsgo": true,
"js/ts.experimental.useTsgo": true
"js/ts.experimental.useTsgo": true,
"js/ts.tsdk.path": "./node_modules/typescript/bin",
"js/ts.tsdk.promptToUseWorkspaceVersion": true,
"js/ts.tsdk.additionalLocations": ["./node_modules/typescript/bin"]
}
}

Expand Down
Loading