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
16 changes: 15 additions & 1 deletion Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,17 @@ export const buildAPI = task({
},
});

async function runCheckAPIDeclarations() {
await run("node", ["./node_modules/typescript/bin/tsc", "-p", "packages/typescript/tsconfig.dist.json"]);
}

export const checkAPIDeclarations = task({
name: "check:api-declarations",
description: "Type-checks the emitted @typescript/typescript declaration files.",
dependencies: [buildAPI],
run: runCheckAPIDeclarations,
});

async function runBuildAPITests(generateSources = true) {
if (generateSources) await runGenerateSync();
await run("npm", ["run", "-w", "@typescript/typescript", "build:test"]);
Expand Down Expand Up @@ -1221,6 +1232,7 @@ export const validate = task({

await runValidation("test:tsc", runTests);
await runValidation("test:extension", runTestExtension);
await runValidation("check:api-declarations", runCheckAPIDeclarations);
if (options.api || options.all) {
if (!options.all) await runGenerateEnums();
await runBuildAPITests(!options.all);
Expand Down Expand Up @@ -2343,6 +2355,7 @@ async function runBuildNativePreviewPackages() {

// Build JS API and copy dist into the package.
await run("npm", ["run", "-w", "@typescript/typescript", "build"]);
await runCheckAPIDeclarations();
await cpRecursive(path.join(inputDir, "dist"), path.join(mainPackageDir, "dist"));

// Validate that .d.ts files contain no external imports (all imports must start with "." or "#").
Expand Down Expand Up @@ -2938,7 +2951,8 @@ async function testVsixPackage(vsixPath, extensionPath, platformPackageName, nod
const require = createRequire(process.cwd() + "/extension/node_modules/typescript/package.json");
const manifest = require("./package.json");
for (const mode of ["async", "sync"]) {
const { API } = await import(pathToFileURL(require.resolve(manifest.name + "/unstable/" + mode)).href);
const exportPath = manifest.exports?.["./" + mode] ? mode : "unstable/" + mode;
const { API } = await import(pathToFileURL(require.resolve(manifest.name + "/" + exportPath)).href);
const api = new API();
try {
assert.ok(await api.parseConfigFile(process.cwd() + "/tsconfig.json"));
Expand Down
26 changes: 13 additions & 13 deletions packages/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,55 +41,55 @@
"./schemas/tsconfig.schema.json": "./schemas/tsconfig.schema.json",
"./schemas/jsconfig.schema.json": "./schemas/jsconfig.schema.json",
".": "./lib/version.cjs",
"./unstable/vscode": {
"./vscode": {
"@typescript/source": "./src/vscode/extensionApi.ts",
"default": "./dist/vscode/extensionApi.js"
},
"./unstable/sync": {
"./sync": {
"@typescript/source": "./src/api/sync/api.ts",
"default": "./dist/api/sync/api.js"
},
"./unstable/async": {
"./async": {
"@typescript/source": "./src/api/async/api.ts",
"default": "./dist/api/async/api.js"
},
"./unstable/fs": {
"./fs": {
"@typescript/source": "./src/api/fs.ts",
"default": "./dist/api/fs.js"
},
"./unstable/path": {
"./path": {
"@typescript/source": "./src/api/typedPaths.ts",
"default": "./dist/api/typedPaths.js"
},
"./unstable/proto": {
"./proto": {
"@typescript/source": "./src/api/proto.ts",
"default": "./dist/api/proto.js"
},
"./unstable/ast": {
"./ast": {
"@typescript/source": "./src/ast/index.ts",
"default": "./dist/ast/index.js"
},
"./unstable/ast/is": {
"./ast/is": {
"@typescript/source": "./src/ast/is.ts",
"default": "./dist/ast/is.js"
},
"./unstable/ast/factory": {
"./ast/factory": {
"@typescript/source": "./src/ast/factory.generated.ts",
"default": "./dist/ast/factory.generated.js"
},
"./unstable/ast/utils": {
"./ast/utils": {
"@typescript/source": "./src/ast/utils.ts",
"default": "./dist/ast/utils.js"
},
"./unstable/ast/scanner": {
"./ast/scanner": {
"@typescript/source": "./src/ast/scanner.ts",
"default": "./dist/ast/scanner.js"
},
"./unstable/ast/visitor": {
"./ast/visitor": {
"@typescript/source": "./src/ast/visitor.ts",
"default": "./dist/ast/visitor.js"
},
"./unstable/ast/clone": {
"./ast/clone": {
"@typescript/source": "./src/ast/clone.ts",
"default": "./dist/ast/clone.js"
}
Expand Down
126 changes: 37 additions & 89 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ import type {
} from "../proto.ts";
import {
resolveFileName,
toCreateSnapshotRequest,
validateSymbolResponse,
} from "../proto.ts";
import {
Expand Down Expand Up @@ -361,13 +360,13 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
private activeBuildOrchestrators: Set<BuildOrchestrator> = new Set();
private activeSourceFileLeases: Map<number, RetainedSourceFile> = new Map();
readonly printer: Printer;
readonly internal: InternalAPI;
readonly debug: DebugHandlers;

constructor(options: APIOptions | LSPConnectionOptions = {}) {
this.client = new Client(options);
this.sourceFileCache = new SourceFileCache<Symbol>();
this.printer = new Printer(this.client);
this.internal = new InternalAPI(this.client, () => this.ensureInitialized()); // @sync: this.internal = new InternalAPI(this.client, this.ensureInitialized);
this.debug = new DebugHandlers(this.client, () => this.ensureInitialized()); // @sync: this.debug = new DebugHandlers(this.client, this.ensureInitialized);
}

/**
Expand Down Expand Up @@ -482,6 +481,7 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
return "\n";
}

/** @internal */
async createBuildOrchestrator(rootNames: readonly string[], buildOrchestratorOptions: BuildOrchestratorOptions): Promise<BuildOrchestrator> {
await this.ensureInitialized();
const orchestratorResponse = await this.client.apiRequest("createBuildOrchestrator", {
Expand Down Expand Up @@ -626,7 +626,7 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
async createSnapshot(params?: CreateSnapshotParams): Promise<Snapshot> {
await this.ensureInitialized();

const requestParams = toCreateSnapshotRequest(this.prepareCreateSnapshotParams(params));
const requestParams = this.prepareCreateSnapshotParams(params);
const data = await this.client.apiRequest("createSnapshot", requestParams);

const snapshot = new Snapshot(
Expand All @@ -653,7 +653,7 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo

const data = await this.client.apiRequest("updateSnapshot", {
snapshot: baseSnapshot.id,
changes: toCreateSnapshotRequest(this.prepareCreateSnapshotParams(params)),
changes: this.prepareCreateSnapshotParams(params),
});
if (data.snapshot === baseSnapshot.id) {
await this.client.apiRequest("release", { snapshot: data.snapshot });
Expand All @@ -675,8 +675,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
return snapshot;
}

private prepareCreateSnapshotParams(params: CreateSnapshotParams | undefined): ProtocolCreateSnapshotParams | undefined {
if (!params) return undefined;
private prepareCreateSnapshotParams(params: CreateSnapshotParams | undefined): ProtocolCreateSnapshotParams {
params ??= {};
const prepareOptions = (options: CreateProgramOptions | undefined): ProtocolCreateProgramOptions | undefined => {
if (!options) return undefined;
const { moduleResolver, projectReferences, ...rest } = options;
Expand Down Expand Up @@ -884,8 +884,13 @@ function sourceFileDescriptor(sourceFile: RemoteSourceFile): SourceFileDescripto
};
}

/** Returns whether a node is backed by a remote compiler response. */
export function isRemoteNode(node: Node): boolean {
return node instanceof RemoteNode;
}

function getRemoteSourceFile(node: Node): RemoteSourceFile | undefined {
if (!(node instanceof RemoteNode)) return undefined;
if (!isRemoteNode(node)) return undefined;
const file = node.getSourceFile();
return file instanceof RemoteSourceFile && file.api ? file : undefined;
}
Expand Down Expand Up @@ -951,11 +956,10 @@ export class RetainedSourceFile {
}
}

export class InternalAPI {
export class DebugHandlers {
private client: Client;
private ensureInitialized: EnsureInitialized;

/** @internal */
constructor(client: Client, ensureInitialized: EnsureInitialized) {
this.client = client;
this.ensureInitialized = ensureInitialized;
Expand Down Expand Up @@ -1051,7 +1055,6 @@ export class Snapshot {
private snapshotRegistry: SnapshotObjectRegistry;
private projectDataMap: Map<ProjectId, ProjectResponse>;
private updateSnapshot: SnapshotUpdater;
readonly internal: SnapshotInternalAPI;

private get client(): Client {
return this.api.client;
Expand Down Expand Up @@ -1083,8 +1086,6 @@ export class Snapshot {
createdPrograms: data.operation.createdPrograms?.map(projectId => this.requireProject(projectId).program),
openedFiles: data.operation.openedFiles?.map(result => ({ project: this.requireProject(result.project) })),
};

this.internal = new SnapshotInternalAPI(this.id, api.client);
}

getProjects(): readonly Project[] {
Expand Down Expand Up @@ -1538,15 +1539,10 @@ export class Project<Id extends ProjectId = ProjectId> {
readonly currentDirectory: RootedDirectoryPath;
readonly dirty: boolean;
readonly parsedCommandLine: ParsedCommandLine;
/** @deprecated Use `parsedCommandLine.options`. */
readonly compilerOptions: CompilerOptions;
/** @deprecated Use `parsedCommandLine.fileNames`. */
readonly rootFiles: readonly RootedFilePath[];

readonly program: Program<Id>;
readonly checker: Checker;
readonly languageService: LanguageService;
private snapshotId: number;

constructor(data: ProjectResponse, snapshotId: number, toPath: (fileName: string, basePath?: string) => PathKey, api: API<boolean>, snapshotRegistry: SnapshotObjectRegistry) {
this.id = data.id as Id;
Expand All @@ -1558,25 +1554,12 @@ export class Project<Id extends ProjectId = ProjectId> {
throw new Error(`Project '${data.configFileName}' has no parsed command line`);
}
this.parsedCommandLine = data.parsedCommandLine;
this.compilerOptions = this.parsedCommandLine.options;
this.rootFiles = this.parsedCommandLine.fileNames;
this.snapshotId = snapshotId;
this.program = new Program(snapshotId, this, toPath);
const objectRegistry = new ProjectObjectRegistry(snapshotId, this, snapshotRegistry);
this.checker = new Checker(snapshotId, this, objectRegistry);
this.languageService = new LanguageService(snapshotId, this, objectRegistry);
}

/** @deprecated Use `languageService.getImportAdderEdits`. */
getImportAdderEdits(file: DocumentIdentifier, actions: readonly APIImportAdderAction[]): Promise<readonly TextEdit[]> {
return this.languageService.getImportAdderEdits(file, actions);
}

/** @deprecated Use `languageService.getImportEditsForSymbols`. */
getImportEditsForSymbols(file: DocumentIdentifier, symbols: readonly Symbol[], options: GetImportEditsForSymbolsOptions = {}): Promise<readonly TextEdit[]> {
return this.languageService.getImportEditsForSymbols(file, symbols, options);
}

dispose(): void {
this.checker.dispose();
}
Expand Down Expand Up @@ -1686,6 +1669,27 @@ export class LanguageService {
})),
};
}

/**
* Format a synthesized node with the correct indentation for insertion at a
* specific position in an existing source file.
*
* @param node The synthesized AST node to format.
* @param file The target file where the node will be inserted.
* @param position The UTF-16 code-unit offset in the target file for insertion.
* @returns The formatted text of the node, indented for the insertion position.
*/
async formatNodeForInsertion(node: Node, file: DocumentIdentifier, position: number): Promise<string> {
const encoded = encodeNode(node);
const base64 = uint8ArrayToBase64(encoded);
return this.client.apiRequest("formatNodeForInsertion", {
snapshot: this.snapshotId,
project: this.project.id,
file,
position,
data: base64,
});
}
}

export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnosticsHost {
Expand Down Expand Up @@ -1723,7 +1727,7 @@ export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnost
}

getNewLine(): string {
return this.project.compilerOptions.newLine === NewLineKind.CRLF ? "\r\n" : "\n";
return this.project.parsedCommandLine.options.newLine === NewLineKind.CRLF ? "\r\n" : "\n";
}

/** @internal */
Expand All @@ -1746,7 +1750,7 @@ export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnost
}

getCompilerOptions(): CompilerOptions {
return this.project.compilerOptions;
return this.project.parsedCommandLine.options;
}

async getSourceFile(file: DocumentIdentifier): Promise<SourceFile | undefined> {
Expand All @@ -1757,8 +1761,6 @@ export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnost

/**
* Returns the source file for an already-canonical path.
*
* @internal
*/
getSourceFileByPath(path: PathKey): Promise<SourceFile | undefined> {
// The wire format is a string, but the cache key remains the supplied
Expand Down Expand Up @@ -2438,21 +2440,6 @@ export class Checker {
return (data ?? []).map(h => new NodeHandle(h, this.project));
}

/** @deprecated Use `project.languageService.getReferencedSymbolsForNode`. */
getReferencedSymbolsForNode(node: Node, position: number): Promise<ReferencedSymbolEntry[]> {
return this.project.languageService.getReferencedSymbolsForNode(node, position);
}

/** @deprecated Use `project.languageService.getSignatureUsage`. */
getSignatureUsage(signatureDecl: Node): Promise<SignatureUsage[]> {
return this.project.languageService.getSignatureUsage(signatureDecl);
}

/** @deprecated Use `project.languageService.getCompletionsAtPosition`. */
getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): Promise<CompletionInfo | undefined> {
return this.project.languageService.getCompletionsAtPosition(document, position, options);
}

/**
* Get the type at a node location. Always returns a type; for nodes whose
* type cannot be determined the checker yields the error type (use
Expand Down Expand Up @@ -3164,45 +3151,6 @@ export class Printer {
}
}

export class SnapshotInternalAPI {
private snapshotId: number;
private client: Client;

constructor(snapshotId: number, client: Client) {
this.snapshotId = snapshotId;
this.client = client;
}

/**
* Format a synthesized node with the correct indentation for insertion at a
* specific position in an existing source file.
*
* @param node The synthesized AST node to format.
* @param file The target file where the node will be inserted.
* @param position The UTF-16 code-unit offset in the target file for insertion.
* @returns The formatted text of the node, indented for the insertion position.
*/
async formatNodeForInsertion(node: Node, file: DocumentIdentifier, position: number): Promise<string> {
const data = await this.client.apiRequest("getDefaultProjectForFile", {
snapshot: this.snapshotId,
file,
});
if (!data) {
throw new Error(`No project found for file: ${typeof file === "string" ? file : file.uri}`);
}

const encoded = encodeNode(node);
const base64 = uint8ArrayToBase64(encoded);
return this.client.apiRequest("formatNodeForInsertion", {
snapshot: this.snapshotId,
project: data.id,
file,
position,
data: base64,
});
}
}

export class NodeHandle<out T extends Node = Node> {
/**
* The project this handle was produced in, used as the default for {@link resolve}.
Expand Down
1 change: 0 additions & 1 deletion packages/typescript/src/api/node/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ export class RemoteSourceFile extends RemoteNode implements SourceFileInfo {
return result;
}

/** @internal */
getOrCreateNodeAtIndex(index: number): Node {
let node = this.nodes[index];
if (!node) {
Expand Down
Loading
Loading