Skip to content
Draft
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
1 change: 1 addition & 0 deletions .claude/worktrees/migrate-emit
Submodule migrate-emit added at e7efb4
1 change: 1 addition & 0 deletions Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ const enumDefs = [
{ name: "TokenFlags", goPrefix: "TokenFlags", goFile: "internal/ast/tokenflags.go", outDir: "_packages/native-preview/src/enums" },
{ name: "NodeBuilderFlags", goPrefix: "Flags", goFile: "internal/nodebuilder/types.go", outDir: "_packages/native-preview/src/enums" },
{ name: "CompletionItemKind", goPrefix: "CompletionItemKind", goFile: "internal/lsp/lsproto/lsp_generated.go", outDir: "_packages/native-preview/src/enums" },
{ name: "EmitOnly", goPrefix: "Emit", goFile: "internal/compiler/emitter.go", outDir: "_packages/native-preview/src/enums" },
// String enum: Go stores internal names with a "\xFE" sentinel prefix, but the escaped
// form sent over the wire uses "__" (see EscapeSymbolName), so map the sentinel accordingly.
{ name: "InternalSymbolName", goPrefix: "InternalSymbolName", goFile: "internal/ast/symbol.go", outDir: "_packages/native-preview/src/enums", stringEnum: true, valueReplacements: { InternalSymbolNamePrefix: "__" } },
Expand Down
19 changes: 17 additions & 2 deletions _packages/native-preview/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { CompletionItemKind } from "#enums/completionItemKind";
import { DiagnosticCategory } from "#enums/diagnosticCategory";
import { ElementFlags } from "#enums/elementFlags";
import { EmitOnly } from "#enums/emitOnly";
import { ModuleKind } from "#enums/moduleKind";
import { NodeBuilderFlags } from "#enums/nodeBuilderFlags";
import { ObjectFlags } from "#enums/objectFlags";
Expand Down Expand Up @@ -87,6 +88,8 @@ import type {
CompletionOptions,
ConditionalType,
Diagnostic,
EmitOutputFile,
EmitResult,
FreshableType,
IdentifierTypePredicate,
IndexedAccessType,
Expand Down Expand Up @@ -115,8 +118,8 @@ import type {
} from "./types.ts";

export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
export type { APIOptions, AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, ClientSocketOptions, ClientSpawnOptions, CompilerOptions, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, DocumentIdentifier, DocumentPosition, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, JSDocTagInfo, LiteralType, LSPConnectionOptions, NumberLiteralType, ObjectType, RequestTiming, SourceFileMetadata, StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TimingAccumulators, TimingInfo, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };
export { CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
export type { APIOptions, AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, ClientSocketOptions, ClientSpawnOptions, CompilerOptions, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, DocumentIdentifier, DocumentPosition, EmitOutputFile, EmitResult, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, JSDocTagInfo, LiteralType, LSPConnectionOptions, NumberLiteralType, ObjectType, RequestTiming, SourceFileMetadata, StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TimingAccumulators, TimingInfo, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };

export class API<FromLSP extends boolean = false> {
private client: Client;
Expand Down Expand Up @@ -798,6 +801,18 @@ export class Program {
});
return data ?? [];
}

async emit(file?: DocumentIdentifier | readonly DocumentIdentifier[], emitOnly?: EmitOnly): Promise<EmitResult> {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
return this.client.apiRequest<EmitResult>("emit", {
snapshot: this.snapshotId,
project: this.project.id,
files,
emitOnly,
});
}
}

export class Checker {
Expand Down
12 changes: 12 additions & 0 deletions _packages/native-preview/src/api/async/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,15 @@ export interface Diagnostic {
/** Related diagnostic information */
readonly relatedInformation?: readonly Diagnostic[] | undefined;
}

export interface EmitOutputFile {
readonly fileName: string;
readonly text: string;
readonly sourceFileName?: string | undefined;
}

export interface EmitResult {
readonly emitSkipped: boolean;
readonly diagnostics: readonly Diagnostic[];
readonly outputFiles: readonly EmitOutputFile[];
}
19 changes: 17 additions & 2 deletions _packages/native-preview/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { CompletionItemKind } from "#enums/completionItemKind";
import { DiagnosticCategory } from "#enums/diagnosticCategory";
import { ElementFlags } from "#enums/elementFlags";
import { EmitOnly } from "#enums/emitOnly";
import { ModuleKind } from "#enums/moduleKind";
import { NodeBuilderFlags } from "#enums/nodeBuilderFlags";
import { ObjectFlags } from "#enums/objectFlags";
Expand Down Expand Up @@ -95,6 +96,8 @@ import type {
CompletionOptions,
ConditionalType,
Diagnostic,
EmitOutputFile,
EmitResult,
FreshableType,
IdentifierTypePredicate,
IndexedAccessType,
Expand Down Expand Up @@ -123,8 +126,8 @@ import type {
} from "./types.ts";

export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
export type { APIOptions, AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, ClientSocketOptions, ClientSpawnOptions, CompilerOptions, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, DocumentIdentifier, DocumentPosition, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, JSDocTagInfo, LiteralType, LSPConnectionOptions, NumberLiteralType, ObjectType, RequestTiming, SourceFileMetadata, StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TimingAccumulators, TimingInfo, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };
export { CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
export type { APIOptions, AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, ClientSocketOptions, ClientSpawnOptions, CompilerOptions, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, DocumentIdentifier, DocumentPosition, EmitOutputFile, EmitResult, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, JSDocTagInfo, LiteralType, LSPConnectionOptions, NumberLiteralType, ObjectType, RequestTiming, SourceFileMetadata, StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TimingAccumulators, TimingInfo, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };

export class API<FromLSP extends boolean = false> {
private client: Client;
Expand Down Expand Up @@ -806,6 +809,18 @@ export class Program {
});
return data ?? [];
}

emit(file?: DocumentIdentifier | readonly DocumentIdentifier[], emitOnly?: EmitOnly): EmitResult {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
return this.client.apiRequest<EmitResult>("emit", {
snapshot: this.snapshotId,
project: this.project.id,
files,
emitOnly,
});
}
}

export class Checker {
Expand Down
12 changes: 12 additions & 0 deletions _packages/native-preview/src/api/sync/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,15 @@ export interface Diagnostic {
/** Related diagnostic information */
readonly relatedInformation?: readonly Diagnostic[] | undefined;
}

export interface EmitOutputFile {
readonly fileName: string;
readonly text: string;
readonly sourceFileName?: string | undefined;
}

export interface EmitResult {
readonly emitSkipped: boolean;
readonly diagnostics: readonly Diagnostic[];
readonly outputFiles: readonly EmitOutputFile[];
}
8 changes: 8 additions & 0 deletions _packages/native-preview/src/enums/emitOnly.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Code generated by Herebyfile.mjs generate:enums from internal/compiler/emitter.go. DO NOT EDIT.

export enum EmitOnly {
All = 0,
OnlyJs = 1,
OnlyDts = 2,
OnlyForcedDts = 3,
}
8 changes: 8 additions & 0 deletions _packages/native-preview/src/enums/emitOnly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Code generated by Herebyfile.mjs generate:enums from internal/compiler/emitter.go. DO NOT EDIT.
export var EmitOnly: any;
(function (EmitOnly) {
EmitOnly[EmitOnly["All"] = 0] = "All";
EmitOnly[EmitOnly["OnlyJs"] = 1] = "OnlyJs";
EmitOnly[EmitOnly["OnlyDts"] = 2] = "OnlyDts";
EmitOnly[EmitOnly["OnlyForcedDts"] = 3] = "OnlyForcedDts";
})(EmitOnly || (EmitOnly = {}));
112 changes: 112 additions & 0 deletions _packages/native-preview/test/async/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
type BigIntLiteralType,
type ConditionalType,
DiagnosticCategory,
EmitOnly,
type FreshableType,
type IndexedAccessType,
type IndexType,
Expand Down Expand Up @@ -4307,6 +4308,117 @@ export const obj = { m: 1, s: "hi", b: true };
});
});

describe("Program - emit", () => {
test("emit single file produces JS output", async () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { target: "es2020" } }),
"/src/a.ts": `export const a: number = 1;`,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const result = await project.program.emit("/src/a.ts");
assert.equal(result.emitSkipped, false);
assert.ok(result.outputFiles.length > 0);
const jsFile = result.outputFiles.find(f => f.fileName.endsWith(".js"));
assert.ok(jsFile, "should produce a .js file");
assert.ok(jsFile.text.includes("export const a = 1"), "JS output should contain the declaration");
assert.equal(jsFile.sourceFileName, "/src/a.ts");
assert.deepEqual(result.diagnostics, []);
}
finally {
await api.close();
}
});

test("emit multiple files produces output for each", async () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { target: "es2020" } }),
"/src/a.ts": `export const a: number = 1;`,
"/src/b.ts": `export const b: string = "hello";`,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const result = await project.program.emit(["/src/a.ts", "/src/b.ts"]);
assert.equal(result.emitSkipped, false);
const jsFiles = result.outputFiles.filter(f => f.fileName.endsWith(".js"));
assert.equal(jsFiles.length, 2);
assert.ok(jsFiles.some(f => f.sourceFileName === "/src/a.ts"));
assert.ok(jsFiles.some(f => f.sourceFileName === "/src/b.ts"));
}
finally {
await api.close();
}
});

test("emit with no file emits all files", async () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { target: "es2020" } }),
"/src/a.ts": `export const a = 1;`,
"/src/b.ts": `export const b = 2;`,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const result = await project.program.emit();
assert.equal(result.emitSkipped, false);
const jsFiles = result.outputFiles.filter(f => f.fileName.endsWith(".js"));
assert.ok(jsFiles.length >= 2, "should emit at least 2 JS files");
}
finally {
await api.close();
}
});

test("emit with empty array returns empty", async () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { target: "es2020" } }),
"/src/a.ts": `export const a = 1;`,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const result = await project.program.emit([]);
assert.equal(result.emitSkipped, false);
assert.deepEqual(result.outputFiles, []);
}
finally {
await api.close();
}
});

test("emit single file respects emitOnly", async () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { target: "es2020", declaration: true } }),
"/src/a.ts": `export const a: number = 1;`,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;

const all = await project.program.emit("/src/a.ts");
assert.equal(all.emitSkipped, false);
assert.ok(all.outputFiles.some(f => f.fileName.endsWith(".js")));
assert.ok(all.outputFiles.some(f => f.fileName.endsWith(".d.ts")));
assert.ok(all.outputFiles.every(f => f.sourceFileName === "/src/a.ts"));

const jsOnly = await project.program.emit("/src/a.ts", EmitOnly.OnlyJs);
assert.equal(jsOnly.emitSkipped, false);
assert.ok(jsOnly.outputFiles.some(f => f.fileName.endsWith(".js")));
assert.ok(!jsOnly.outputFiles.some(f => f.fileName.endsWith(".d.ts")));

const dtsOnly = await project.program.emit("/src/a.ts", EmitOnly.OnlyDts);
assert.equal(dtsOnly.emitSkipped, false);
assert.ok(dtsOnly.outputFiles.some(f => f.fileName.endsWith(".d.ts")));
assert.ok(!dtsOnly.outputFiles.some(f => f.fileName.endsWith(".js")));
}
finally {
await api.close();
}
});
});

describe("modifierFlags", () => {
test("export async function has Export | Async flags", async () => {
const api = spawnAPI({
Expand Down
Loading