diff --git a/cli/build.rs b/cli/build.rs
index 62f7101942..742f227ec9 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -122,10 +122,16 @@ mod ts {
deno_core::extension!(deno_tsc,
ops = [op_build_info, op_is_node_file, op_load, op_script_version],
+ esm_entry_point = "ext:deno_tsc/99_main_compiler.js",
+ esm = [
+ dir "tsc",
+ "97_ts_host.js",
+ "98_lsp.js",
+ "99_main_compiler.js",
+ ],
js = [
dir "tsc",
"00_typescript.js",
- "99_main_compiler.js",
],
options = {
op_crate_libs: HashMap<&'static str, PathBuf>,
diff --git a/cli/tsc/97_ts_host.js b/cli/tsc/97_ts_host.js
new file mode 100644
index 0000000000..ba82a12b7c
--- /dev/null
+++ b/cli/tsc/97_ts_host.js
@@ -0,0 +1,832 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+// @ts-check
+///
+// deno-lint-ignore-file no-undef
+
+/** @type {DenoCore} */
+const core = globalThis.Deno.core;
+const ops = core.ops;
+
+let logDebug = false;
+let logSource = "JS";
+
+// The map from the normalized specifier to the original.
+// TypeScript normalizes the specifier in its internal processing,
+// but the original specifier is needed when looking up the source from the runtime.
+// This map stores that relationship, and the original can be restored by the
+// normalized specifier.
+// See: https://github.com/denoland/deno/issues/9277#issuecomment-769653834
+/** @type {Map} */
+const normalizedToOriginalMap = new Map();
+
+/** @type {ReadonlySet} */
+const unstableDenoProps = new Set([
+ "AtomicOperation",
+ "DatagramConn",
+ "Kv",
+ "KvListIterator",
+ "KvU64",
+ "UnixConnectOptions",
+ "UnixListenOptions",
+ "listen",
+ "listenDatagram",
+ "openKv",
+ "connectQuic",
+ "listenQuic",
+ "QuicBidirectionalStream",
+ "QuicConn",
+ "QuicListener",
+ "QuicReceiveStream",
+ "QuicSendStream",
+]);
+const unstableMsgSuggestion =
+ "If not, try changing the 'lib' compiler option to include 'deno.unstable' " +
+ "or add a triple-slash directive to the top of your entrypoint (main file): " +
+ '/// ';
+
+/**
+ * @param {unknown} value
+ * @returns {value is ts.CreateSourceFileOptions}
+ */
+function isCreateSourceFileOptions(value) {
+ return value != null && typeof value === "object" &&
+ "languageVersion" in value;
+}
+
+/**
+ * @param {ts.ScriptTarget | ts.CreateSourceFileOptions | undefined} versionOrOptions
+ * @returns {ts.CreateSourceFileOptions}
+ */
+export function getCreateSourceFileOptions(versionOrOptions) {
+ return isCreateSourceFileOptions(versionOrOptions)
+ ? versionOrOptions
+ : { languageVersion: versionOrOptions ?? ts.ScriptTarget.ESNext };
+}
+
+/**
+ * @param debug {boolean}
+ * @param source {string}
+ */
+export function setLogDebug(debug, source) {
+ logDebug = debug;
+ if (source) {
+ logSource = source;
+ }
+}
+
+/** @param msg {string} */
+function printStderr(msg) {
+ core.print(msg, true);
+}
+
+/** @param args {any[]} */
+export function debug(...args) {
+ if (logDebug) {
+ const stringifiedArgs = args.map((arg) =>
+ typeof arg === "string" ? arg : JSON.stringify(arg)
+ ).join(" ");
+ printStderr(`DEBUG ${logSource} - ${stringifiedArgs}\n`);
+ }
+}
+
+/** @param args {any[]} */
+export function error(...args) {
+ const stringifiedArgs = args.map((arg) =>
+ typeof arg === "string" || arg instanceof Error
+ ? String(arg)
+ : JSON.stringify(arg)
+ ).join(" ");
+ printStderr(`ERROR ${logSource} = ${stringifiedArgs}\n`);
+}
+
+export class AssertionError extends Error {
+ /** @param msg {string} */
+ constructor(msg) {
+ super(msg);
+ this.name = "AssertionError";
+ }
+}
+
+/** @param cond {boolean} */
+export function assert(cond, msg = "Assertion failed.") {
+ if (!cond) {
+ throw new AssertionError(msg);
+ }
+}
+
+// In the case of the LSP, this will only ever contain the assets.
+/** @type {Map} */
+export const SOURCE_FILE_CACHE = new Map();
+
+/** @type {Map} */
+export const SCRIPT_SNAPSHOT_CACHE = new Map();
+
+/** @type {Map} */
+export const SOURCE_REF_COUNTS = new Map();
+
+/** @type {Map} */
+export const SCRIPT_VERSION_CACHE = new Map();
+
+/** @type {Map} */
+export const IS_NODE_SOURCE_FILE_CACHE = new Map();
+
+// Maps asset specifiers to the first scope that the asset was loaded into.
+/** @type {Map} */
+export const ASSET_SCOPES = new Map();
+
+/** @type {number | null} */
+let projectVersionCache = null;
+export const PROJECT_VERSION_CACHE = {
+ get: () => projectVersionCache,
+ set: (version) => {
+ projectVersionCache = version;
+ },
+};
+
+/** @type {string | null} */
+let lastRequestMethod = null;
+export const LAST_REQUEST_METHOD = {
+ get: () => lastRequestMethod,
+ set: (method) => {
+ lastRequestMethod = method;
+ },
+};
+
+/** @type {string | null} */
+let lastRequestScope = null;
+export const LAST_REQUEST_SCOPE = {
+ get: () => lastRequestScope,
+ set: (scope) => {
+ lastRequestScope = scope;
+ },
+};
+
+ts.deno.setIsNodeSourceFileCallback((sourceFile) => {
+ const fileName = sourceFile.fileName;
+ let isNodeSourceFile = IS_NODE_SOURCE_FILE_CACHE.get(fileName);
+ if (isNodeSourceFile == null) {
+ const result = ops.op_is_node_file(fileName);
+ isNodeSourceFile = /** @type {boolean} */ (result);
+ IS_NODE_SOURCE_FILE_CACHE.set(fileName, isNodeSourceFile);
+ }
+ return isNodeSourceFile;
+});
+
+/**
+ * @param msg {string}
+ * @param code {number}
+ */
+function formatMessage(msg, code) {
+ switch (code) {
+ case 2304: {
+ if (msg === "Cannot find name 'Deno'.") {
+ msg += " Do you need to change your target library? " +
+ "Try changing the 'lib' compiler option to include 'deno.ns' " +
+ "or add a triple-slash directive to the top of your entrypoint " +
+ '(main file): /// ';
+ }
+ return msg;
+ }
+ case 2339: {
+ const property = getProperty();
+ if (property && unstableDenoProps.has(property)) {
+ return `${msg} 'Deno.${property}' is an unstable API. ${unstableMsgSuggestion}`;
+ }
+ return msg;
+ }
+ default: {
+ const property = getProperty();
+ if (property && unstableDenoProps.has(property)) {
+ const suggestion = getMsgSuggestion();
+ if (suggestion) {
+ return `${msg} 'Deno.${property}' is an unstable API. Did you mean '${suggestion}'? ${unstableMsgSuggestion}`;
+ }
+ }
+ return msg;
+ }
+ }
+
+ function getProperty() {
+ return /Property '([^']+)' does not exist on type 'typeof Deno'/
+ .exec(msg)?.[1];
+ }
+
+ function getMsgSuggestion() {
+ return / Did you mean '([^']+)'\?/.exec(msg)?.[1];
+ }
+}
+
+/** @param {ts.DiagnosticRelatedInformation} diagnostic */
+function fromRelatedInformation({
+ start,
+ length,
+ file,
+ messageText: msgText,
+ ...ri
+}) {
+ let messageText;
+ let messageChain;
+ if (typeof msgText === "object") {
+ messageChain = msgText;
+ } else {
+ messageText = formatMessage(msgText, ri.code);
+ }
+ if (start !== undefined && length !== undefined && file) {
+ let startPos = file.getLineAndCharacterOfPosition(start);
+ let sourceLine = file.getFullText().split("\n")[startPos.line];
+ const originalFileName = file.fileName;
+ const fileName = ops.op_remap_specifier
+ ? (ops.op_remap_specifier(file.fileName) ?? file.fileName)
+ : file.fileName;
+ // Bit of a hack to detect when we have a .wasm file and want to hide
+ // the .d.ts text. This is not perfect, but will work in most scenarios
+ if (
+ fileName.endsWith(".wasm") && originalFileName.endsWith(".wasm.d.mts")
+ ) {
+ startPos = { line: 0, character: 0 };
+ sourceLine = undefined;
+ }
+ return {
+ start: startPos,
+ end: file.getLineAndCharacterOfPosition(start + length),
+ fileName,
+ messageChain,
+ messageText,
+ sourceLine,
+ ...ri,
+ };
+ } else {
+ return {
+ messageChain,
+ messageText,
+ ...ri,
+ };
+ }
+}
+
+/** @param {readonly ts.Diagnostic[]} diagnostics */
+export function fromTypeScriptDiagnostics(diagnostics) {
+ return diagnostics.map(({ relatedInformation: ri, source, ...diag }) => {
+ /** @type {any} */
+ const value = fromRelatedInformation(diag);
+ value.relatedInformation = ri ? ri.map(fromRelatedInformation) : undefined;
+ value.source = source;
+ return value;
+ });
+}
+
+// Using incremental compile APIs requires that all
+// paths must be either relative or absolute. Since
+// analysis in Rust operates on fully resolved URLs,
+// it makes sense to use the same scheme here.
+export const ASSETS_URL_PREFIX = "asset:///";
+const CACHE_URL_PREFIX = "cache:///";
+
+/** Diagnostics that are intentionally ignored when compiling TypeScript in
+ * Deno, as they provide misleading or incorrect information. */
+const IGNORED_DIAGNOSTICS = [
+ // TS1452: 'resolution-mode' assertions are only supported when `moduleResolution` is `node16` or `nodenext`.
+ // We specify the resolution mode to be CommonJS for some npm files and this
+ // diagnostic gets generated even though we're using custom module resolution.
+ 1452,
+ // Module '...' cannot be imported using this construct. The specifier only resolves to an
+ // ES module, which cannot be imported with 'require'.
+ 1471,
+ // TS1479: The current file is a CommonJS module whose imports will produce 'require' calls;
+ // however, the referenced file is an ECMAScript module and cannot be imported with 'require'.
+ 1479,
+ // TS2306: File '.../index.d.ts' is not a module.
+ // We get this for `x-typescript-types` declaration files which don't export
+ // anything. We prefer to treat these as modules with no exports.
+ 2306,
+ // TS2688: Cannot find type definition file for '...'.
+ // We ignore because type definition files can end with '.ts'.
+ 2688,
+ // TS2792: Cannot find module. Did you mean to set the 'moduleResolution'
+ // option to 'node', or to add aliases to the 'paths' option?
+ 2792,
+ // TS2307: Cannot find module '{0}' or its corresponding type declarations.
+ 2307,
+ // Relative import errors to add an extension
+ 2834,
+ 2835,
+ // TS5009: Cannot find the common subdirectory path for the input files.
+ 5009,
+ // TS5055: Cannot write file
+ // 'http://localhost:4545/subdir/mt_application_x_javascript.j4.js'
+ // because it would overwrite input file.
+ 5055,
+ // TypeScript is overly opinionated that only CommonJS modules kinds can
+ // support JSON imports. Allegedly this was fixed in
+ // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
+ // so we will ignore complaints about this compiler setting.
+ 5070,
+ // TS6053: File '{0}' not found.
+ 6053,
+ // TS7016: Could not find a declaration file for module '...'. '...'
+ // implicitly has an 'any' type. This is due to `allowJs` being off by
+ // default but importing of a JavaScript module.
+ 7016,
+];
+
+// todo(dsherret): can we remove this and just use ts.OperationCanceledException?
+/** Error thrown on cancellation. */
+export class OperationCanceledError extends Error {
+}
+
+/**
+ * This implementation calls into Rust to check if Tokio's cancellation token
+ * has already been canceled.
+ * @implements {ts.CancellationToken}
+ */
+class CancellationToken {
+ isCancellationRequested() {
+ return ops.op_is_cancelled();
+ }
+
+ throwIfCancellationRequested() {
+ if (this.isCancellationRequested()) {
+ throw new OperationCanceledError();
+ }
+ }
+}
+
+/** @typedef {{
+ * ls: ts.LanguageService & { [k:string]: any },
+ * compilerOptions: ts.CompilerOptions,
+ * }} LanguageServiceEntry */
+/** @type {{ unscoped: LanguageServiceEntry, byScope: Map }} */
+export const LANGUAGE_SERVICE_ENTRIES = {
+ // @ts-ignore Will be set later.
+ unscoped: null,
+ byScope: new Map(),
+};
+
+/** @type {{ unscoped: string[], byScope: Map } | null} */
+let SCRIPT_NAMES_CACHE = null;
+
+export function clearScriptNamesCache() {
+ SCRIPT_NAMES_CACHE = null;
+}
+
+/** An object literal of the incremental compiler host, which provides the
+ * specific "bindings" to the Deno environment that tsc needs to work.
+ *
+ * @type {ts.CompilerHost & ts.LanguageServiceHost} */
+export const host = {
+ fileExists(specifier) {
+ if (logDebug) {
+ debug(`host.fileExists("${specifier}")`);
+ }
+ // TODO(bartlomieju): is this assumption still valid?
+ // this is used by typescript to find the libs path
+ // so we can completely ignore it
+ return false;
+ },
+ readFile(specifier) {
+ if (logDebug) {
+ debug(`host.readFile("${specifier}")`);
+ }
+ return ops.op_load(specifier)?.data;
+ },
+ getCancellationToken() {
+ // createLanguageService will call this immediately and cache it
+ return new CancellationToken();
+ },
+ getProjectVersion() {
+ const cachedProjectVersion = PROJECT_VERSION_CACHE.get();
+ if (
+ cachedProjectVersion
+ ) {
+ debug(`getProjectVersion cache hit : ${cachedProjectVersion}`);
+ return cachedProjectVersion;
+ }
+ const projectVersion = ops.op_project_version();
+ PROJECT_VERSION_CACHE.set(projectVersion);
+ debug(`getProjectVersion cache miss : ${projectVersion}`);
+ return projectVersion;
+ },
+ // @ts-ignore Undocumented method.
+ getModuleSpecifierCache() {
+ return moduleSpecifierCache;
+ },
+ // @ts-ignore Undocumented method.
+ getCachedExportInfoMap() {
+ return exportMapCache;
+ },
+ getGlobalTypingsCacheLocation() {
+ return undefined;
+ },
+ // @ts-ignore Undocumented method.
+ toPath(fileName) {
+ // @ts-ignore Undocumented function.
+ ts.toPath(
+ fileName,
+ this.getCurrentDirectory(),
+ this.getCanonicalFileName.bind(this),
+ );
+ },
+ // @ts-ignore Undocumented method.
+ watchNodeModulesForPackageJsonChanges() {
+ return { close() {} };
+ },
+ getSourceFile(
+ specifier,
+ languageVersion,
+ _onError,
+ // this is not used by the lsp because source
+ // files are created in the document registry
+ _shouldCreateNewSourceFile,
+ ) {
+ if (logDebug) {
+ debug(
+ `host.getSourceFile("${specifier}", ${
+ ts.ScriptTarget[
+ getCreateSourceFileOptions(languageVersion).languageVersion
+ ]
+ })`,
+ );
+ }
+
+ // Needs the original specifier
+ specifier = normalizedToOriginalMap.get(specifier) ?? specifier;
+
+ let sourceFile = SOURCE_FILE_CACHE.get(specifier);
+ if (sourceFile) {
+ return sourceFile;
+ }
+
+ /** @type {{ data: string; scriptKind: ts.ScriptKind; version: string; isCjs: boolean }} */
+ const fileInfo = ops.op_load(specifier);
+ if (!fileInfo) {
+ return undefined;
+ }
+ const { data, scriptKind, version, isCjs } = fileInfo;
+ assert(
+ data != null,
+ `"data" is unexpectedly null for "${specifier}".`,
+ );
+
+ sourceFile = ts.createSourceFile(
+ specifier,
+ data,
+ {
+ ...getCreateSourceFileOptions(languageVersion),
+ impliedNodeFormat: isCjs
+ ? ts.ModuleKind.CommonJS
+ : ts.ModuleKind.ESNext,
+ // no need to parse docs for `deno check`
+ jsDocParsingMode: ts.JSDocParsingMode.ParseForTypeErrors,
+ },
+ false,
+ scriptKind,
+ );
+ sourceFile.moduleName = specifier;
+ sourceFile.version = version;
+ if (specifier.startsWith(ASSETS_URL_PREFIX)) {
+ sourceFile.version = "1";
+ }
+ SOURCE_FILE_CACHE.set(specifier, sourceFile);
+ SCRIPT_VERSION_CACHE.set(specifier, version);
+ return sourceFile;
+ },
+ getDefaultLibFileName() {
+ return `${ASSETS_URL_PREFIX}lib.esnext.d.ts`;
+ },
+ getDefaultLibLocation() {
+ return ASSETS_URL_PREFIX;
+ },
+ writeFile(fileName, data, _writeByteOrderMark, _onError, _sourceFiles) {
+ if (logDebug) {
+ debug(`host.writeFile("${fileName}")`);
+ }
+ return ops.op_emit(
+ data,
+ fileName,
+ );
+ },
+ getCurrentDirectory() {
+ if (logDebug) {
+ debug(`host.getCurrentDirectory()`);
+ }
+ return CACHE_URL_PREFIX;
+ },
+ getCanonicalFileName(fileName) {
+ return fileName;
+ },
+ useCaseSensitiveFileNames() {
+ return true;
+ },
+ getNewLine() {
+ return "\n";
+ },
+ resolveTypeReferenceDirectiveReferences(
+ typeDirectiveReferences,
+ containingFilePath,
+ _redirectedReference,
+ options,
+ containingSourceFile,
+ _reusedNames,
+ ) {
+ const isCjs =
+ containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS;
+ const toResolve = typeDirectiveReferences.map((arg) => {
+ /** @type {ts.FileReference} */
+ const fileReference = typeof arg === "string"
+ ? {
+ pos: -1,
+ end: -1,
+ fileName: arg,
+ }
+ : arg;
+ return [
+ fileReference.resolutionMode == null
+ ? isCjs
+ : fileReference.resolutionMode === ts.ModuleKind.CommonJS,
+ fileReference.fileName,
+ ];
+ });
+
+ /** @type {Array<[string, ts.Extension | null] | undefined>} */
+ const resolved = ops.op_resolve(
+ containingFilePath,
+ toResolve,
+ );
+
+ /** @type {Array} */
+ const result = resolved.map((item) => {
+ if (item && item[1]) {
+ const [resolvedFileName, extension] = item;
+ return {
+ resolvedTypeReferenceDirective: {
+ primary: true,
+ resolvedFileName,
+ extension,
+ isExternalLibraryImport: false,
+ },
+ };
+ } else {
+ return {
+ resolvedTypeReferenceDirective: undefined,
+ };
+ }
+ });
+
+ if (logDebug) {
+ debug(
+ "resolveTypeReferenceDirectiveReferences ",
+ typeDirectiveReferences,
+ containingFilePath,
+ options,
+ containingSourceFile?.fileName,
+ " => ",
+ result,
+ );
+ }
+ return result;
+ },
+ resolveModuleNameLiterals(
+ moduleLiterals,
+ base,
+ _redirectedReference,
+ compilerOptions,
+ containingSourceFile,
+ _reusedNames,
+ ) {
+ const specifiers = moduleLiterals.map((literal) => [
+ ts.getModeForUsageLocation(
+ containingSourceFile,
+ literal,
+ compilerOptions,
+ ) === ts.ModuleKind.CommonJS,
+ literal.text,
+ ]);
+ if (logDebug) {
+ debug(`host.resolveModuleNames()`);
+ debug(` base: ${base}`);
+ debug(` specifiers: ${specifiers.map((s) => s[1]).join(", ")}`);
+ }
+ /** @type {Array<[string, ts.Extension | null] | undefined>} */
+ const resolved = ops.op_resolve(
+ base,
+ specifiers,
+ );
+ if (resolved) {
+ /** @type {Array} */
+ const result = resolved.map((item) => {
+ if (item && item[1]) {
+ const [resolvedFileName, extension] = item;
+ return {
+ resolvedModule: {
+ resolvedFileName,
+ extension,
+ // todo(dsherret): we should probably be setting this
+ isExternalLibraryImport: false,
+ },
+ };
+ }
+ return {
+ resolvedModule: undefined,
+ };
+ });
+ result.length = specifiers.length;
+ return result;
+ } else {
+ return new Array(specifiers.length);
+ }
+ },
+ createHash(data) {
+ return ops.op_create_hash(data);
+ },
+
+ // LanguageServiceHost
+ getCompilationSettings() {
+ if (logDebug) {
+ debug("host.getCompilationSettings()");
+ }
+ const lastRequestScope = LAST_REQUEST_SCOPE.get();
+ return (lastRequestScope
+ ? LANGUAGE_SERVICE_ENTRIES.byScope.get(lastRequestScope)
+ ?.compilerOptions
+ : null) ?? LANGUAGE_SERVICE_ENTRIES.unscoped.compilerOptions;
+ },
+ getScriptFileNames() {
+ if (logDebug) {
+ debug("host.getScriptFileNames()");
+ }
+ if (!SCRIPT_NAMES_CACHE) {
+ const { unscoped, byScope } = ops.op_script_names();
+ SCRIPT_NAMES_CACHE = {
+ unscoped,
+ byScope: new Map(Object.entries(byScope)),
+ };
+ }
+ const lastRequestScope = LAST_REQUEST_SCOPE.get();
+ return (lastRequestScope
+ ? SCRIPT_NAMES_CACHE.byScope.get(lastRequestScope)
+ : null) ?? SCRIPT_NAMES_CACHE.unscoped;
+ },
+ getScriptVersion(specifier) {
+ if (logDebug) {
+ debug(`host.getScriptVersion("${specifier}")`);
+ }
+ if (specifier.startsWith(ASSETS_URL_PREFIX)) {
+ return "1";
+ }
+ // tsc requests the script version multiple times even though it can't
+ // possibly have changed, so we will memoize it on a per request basis.
+ if (SCRIPT_VERSION_CACHE.has(specifier)) {
+ return SCRIPT_VERSION_CACHE.get(specifier);
+ }
+ const scriptVersion = ops.op_script_version(specifier);
+ SCRIPT_VERSION_CACHE.set(specifier, scriptVersion);
+ return scriptVersion;
+ },
+ getScriptSnapshot(specifier) {
+ if (logDebug) {
+ debug(`host.getScriptSnapshot("${specifier}")`);
+ }
+ if (specifier.startsWith(ASSETS_URL_PREFIX)) {
+ const sourceFile = this.getSourceFile(
+ specifier,
+ ts.ScriptTarget.ESNext,
+ );
+ if (sourceFile) {
+ if (!ASSET_SCOPES.has(specifier)) {
+ ASSET_SCOPES.set(specifier, LAST_REQUEST_SCOPE.get());
+ }
+ // This case only occurs for assets.
+ return ts.ScriptSnapshot.fromString(sourceFile.text);
+ }
+ }
+ let scriptSnapshot = SCRIPT_SNAPSHOT_CACHE.get(specifier);
+ if (scriptSnapshot == undefined) {
+ /** @type {{ data: string, version: string, isCjs: boolean }} */
+ const fileInfo = ops.op_load(specifier);
+ if (!fileInfo) {
+ return undefined;
+ }
+ scriptSnapshot = ts.ScriptSnapshot.fromString(fileInfo.data);
+ scriptSnapshot.isCjs = fileInfo.isCjs;
+ SCRIPT_SNAPSHOT_CACHE.set(specifier, scriptSnapshot);
+ SCRIPT_VERSION_CACHE.set(specifier, fileInfo.version);
+ }
+ return scriptSnapshot;
+ },
+};
+
+// @ts-ignore Undocumented function.
+const moduleSpecifierCache = ts.server.createModuleSpecifierCache(host);
+
+// @ts-ignore Undocumented function.
+const exportMapCache = ts.createCacheableExportInfoMap(host);
+
+// override the npm install @types package diagnostics to be deno specific
+ts.setLocalizedDiagnosticMessages((() => {
+ const nodeMessage = "Cannot find name '{0}'."; // don't offer any suggestions
+ const jqueryMessage =
+ "Cannot find name '{0}'. Did you mean to import jQuery? Try adding `import $ from \"npm:jquery\";`.";
+ return {
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashno_2580":
+ nodeMessage,
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashno_2591":
+ nodeMessage,
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slash_2581":
+ jqueryMessage,
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slash_2592":
+ jqueryMessage,
+ "Module_0_was_resolved_to_1_but_allowArbitraryExtensions_is_not_set_6263":
+ "Module '{0}' was resolved to '{1}', but importing these modules is not supported.",
+ };
+})());
+
+/** @param {ts.Diagnostic} diagnostic */
+export function filterMapDiagnostic(diagnostic) {
+ if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) {
+ return false;
+ }
+
+ // ignore diagnostics resulting from the `ImportMeta` declaration in deno merging with
+ // the one in @types/node. the types of the filename and dirname properties are different,
+ // which causes tsc to error.
+ const importMetaFilenameDirnameModifiersRe =
+ /^All declarations of '(filename|dirname)'/;
+ const importMetaFilenameDirnameTypesRe =
+ /^Subsequent property declarations must have the same type.\s+Property '(filename|dirname)'/;
+ // Declarations of X must have identical modifiers.
+ if (diagnostic.code === 2687) {
+ if (
+ typeof diagnostic.messageText === "string" &&
+ (importMetaFilenameDirnameModifiersRe.test(diagnostic.messageText)) &&
+ (diagnostic.file?.fileName.startsWith("asset:///") ||
+ diagnostic.file?.fileName?.includes("@types/node"))
+ ) {
+ return false;
+ }
+ }
+ // Subsequent property declarations must have the same type.
+ if (diagnostic.code === 2717) {
+ if (
+ typeof diagnostic.messageText === "string" &&
+ (importMetaFilenameDirnameTypesRe.test(diagnostic.messageText)) &&
+ (diagnostic.file?.fileName.startsWith("asset:///") ||
+ diagnostic.file?.fileName?.includes("@types/node"))
+ ) {
+ return false;
+ }
+ }
+ // make the diagnostic for using an `export =` in an es module a warning
+ if (diagnostic.code === 1203) {
+ diagnostic.category = ts.DiagnosticCategory.Warning;
+ if (typeof diagnostic.messageText === "string") {
+ const message =
+ " This will start erroring in a future version of Deno 2 " +
+ "in order to align with TypeScript.";
+ // seems typescript shares objects, so check if it's already been set
+ if (!diagnostic.messageText.endsWith(message)) {
+ diagnostic.messageText += message;
+ }
+ }
+ }
+ return true;
+}
+
+// list of globals that should be kept in Node's globalThis
+ts.deno.setNodeOnlyGlobalNames([
+ "__dirname",
+ "__filename",
+ "Buffer",
+ "BufferConstructor",
+ "BufferEncoding",
+ "clearImmediate",
+ "clearInterval",
+ "clearTimeout",
+ "console",
+ "Console",
+ "ErrorConstructor",
+ "gc",
+ "Global",
+ "localStorage",
+ "queueMicrotask",
+ "RequestInit",
+ "ResponseInit",
+ "sessionStorage",
+ "setImmediate",
+ "setInterval",
+ "setTimeout",
+]);
+
+export function getAssets() {
+ /** @type {{ specifier: string; text: string; }[]} */
+ const assets = [];
+ for (const sourceFile of SOURCE_FILE_CACHE.values()) {
+ if (sourceFile.fileName.startsWith(ASSETS_URL_PREFIX)) {
+ assets.push({
+ specifier: sourceFile.fileName,
+ text: sourceFile.text,
+ });
+ }
+ }
+ return assets;
+}
diff --git a/cli/tsc/98_lsp.js b/cli/tsc/98_lsp.js
new file mode 100644
index 0000000000..e9be7cc123
--- /dev/null
+++ b/cli/tsc/98_lsp.js
@@ -0,0 +1,486 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+import {
+ ASSET_SCOPES,
+ ASSETS_URL_PREFIX,
+ clearScriptNamesCache,
+ debug,
+ error,
+ filterMapDiagnostic,
+ fromTypeScriptDiagnostics,
+ getAssets,
+ getCreateSourceFileOptions,
+ host,
+ IS_NODE_SOURCE_FILE_CACHE,
+ LANGUAGE_SERVICE_ENTRIES,
+ LAST_REQUEST_METHOD,
+ LAST_REQUEST_SCOPE,
+ OperationCanceledError,
+ PROJECT_VERSION_CACHE,
+ SCRIPT_SNAPSHOT_CACHE,
+ SCRIPT_VERSION_CACHE,
+ setLogDebug,
+ SOURCE_REF_COUNTS,
+} from "./97_ts_host.js";
+
+/** @type {DenoCore} */
+const core = globalThis.Deno.core;
+const ops = core.ops;
+
+const ChangeKind = {
+ Opened: 0,
+ Modified: 1,
+ Closed: 2,
+};
+
+/**
+ * @param {ts.CompilerOptions | ts.MinimalResolutionCacheHost} settingsOrHost
+ * @returns {ts.CompilerOptions}
+ */
+function getCompilationSettings(settingsOrHost) {
+ if (typeof settingsOrHost.getCompilationSettings === "function") {
+ return settingsOrHost.getCompilationSettings();
+ }
+ return /** @type {ts.CompilerOptions} */ (settingsOrHost);
+}
+
+// We need to use a custom document registry in order to provide source files
+// with an impliedNodeFormat to the ts language service
+
+/** @type {Map} */
+const documentRegistrySourceFileCache = new Map();
+const { getKeyForCompilationSettings } = ts.createDocumentRegistry(); // reuse this code
+/** @type {ts.DocumentRegistry} */
+const documentRegistry = {
+ acquireDocument(
+ fileName,
+ compilationSettingsOrHost,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const key = getKeyForCompilationSettings(
+ getCompilationSettings(compilationSettingsOrHost),
+ );
+ return this.acquireDocumentWithKey(
+ fileName,
+ /** @type {ts.Path} */ (fileName),
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ );
+ },
+
+ acquireDocumentWithKey(
+ fileName,
+ path,
+ _compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const mapKey = path + key;
+ let sourceFile = documentRegistrySourceFileCache.get(mapKey);
+ if (!sourceFile || sourceFile.version !== version) {
+ const isCjs = /** @type {any} */ (scriptSnapshot).isCjs;
+ sourceFile = ts.createLanguageServiceSourceFile(
+ fileName,
+ scriptSnapshot,
+ {
+ ...getCreateSourceFileOptions(sourceFileOptions),
+ impliedNodeFormat: isCjs
+ ? ts.ModuleKind.CommonJS
+ : ts.ModuleKind.ESNext,
+ // in the lsp we want to be able to show documentation
+ jsDocParsingMode: ts.JSDocParsingMode.ParseAll,
+ },
+ version,
+ true,
+ scriptKind,
+ );
+ documentRegistrySourceFileCache.set(mapKey, sourceFile);
+ }
+ const sourceRefCount = SOURCE_REF_COUNTS.get(fileName) ?? 0;
+ SOURCE_REF_COUNTS.set(fileName, sourceRefCount + 1);
+ return sourceFile;
+ },
+
+ updateDocument(
+ fileName,
+ compilationSettingsOrHost,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const key = getKeyForCompilationSettings(
+ getCompilationSettings(compilationSettingsOrHost),
+ );
+ return this.updateDocumentWithKey(
+ fileName,
+ /** @type {ts.Path} */ (fileName),
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ );
+ },
+
+ updateDocumentWithKey(
+ fileName,
+ path,
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const mapKey = path + key;
+ let sourceFile = documentRegistrySourceFileCache.get(mapKey) ??
+ this.acquireDocumentWithKey(
+ fileName,
+ path,
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ );
+
+ if (sourceFile.version !== version) {
+ sourceFile = ts.updateLanguageServiceSourceFile(
+ sourceFile,
+ scriptSnapshot,
+ version,
+ scriptSnapshot.getChangeRange(
+ /** @type {ts.IScriptSnapshot} */ (sourceFile.scriptSnapShot),
+ ),
+ );
+ documentRegistrySourceFileCache.set(mapKey, sourceFile);
+ }
+ return sourceFile;
+ },
+
+ getKeyForCompilationSettings(settings) {
+ return getKeyForCompilationSettings(settings);
+ },
+
+ releaseDocument(
+ fileName,
+ compilationSettings,
+ scriptKind,
+ impliedNodeFormat,
+ ) {
+ const key = getKeyForCompilationSettings(compilationSettings);
+ return this.releaseDocumentWithKey(
+ /** @type {ts.Path} */ (fileName),
+ key,
+ scriptKind,
+ impliedNodeFormat,
+ );
+ },
+
+ releaseDocumentWithKey(path, key, _scriptKind, _impliedNodeFormat) {
+ const sourceRefCount = SOURCE_REF_COUNTS.get(path) ?? 1;
+ if (sourceRefCount <= 1) {
+ SOURCE_REF_COUNTS.delete(path);
+ // We call `cleanupSemanticCache` for other purposes, don't bust the
+ // source cache in this case.
+ if (LAST_REQUEST_METHOD.get() != "cleanupSemanticCache") {
+ const mapKey = path + key;
+ documentRegistrySourceFileCache.delete(mapKey);
+ SCRIPT_SNAPSHOT_CACHE.delete(path);
+ ops.op_release(path);
+ }
+ } else {
+ SOURCE_REF_COUNTS.set(path, sourceRefCount - 1);
+ }
+ },
+
+ reportStats() {
+ return "[]";
+ },
+};
+
+/** @param {Record} config */
+function normalizeConfig(config) {
+ // the typescript compiler doesn't know about the precompile
+ // transform at the moment, so just tell it we're using react-jsx
+ if (config.jsx === "precompile") {
+ config.jsx = "react-jsx";
+ }
+ if (config.jsxPrecompileSkipElements) {
+ delete config.jsxPrecompileSkipElements;
+ }
+ return config;
+}
+
+/** @param {Record} config */
+function lspTsConfigToCompilerOptions(config) {
+ const normalizedConfig = normalizeConfig(config);
+ const { options, errors } = ts
+ .convertCompilerOptionsFromJson(normalizedConfig, "");
+ Object.assign(options, {
+ allowNonTsExtensions: true,
+ allowImportingTsExtensions: true,
+ module: ts.ModuleKind.NodeNext,
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
+ });
+ if (errors.length > 0) {
+ debug(ts.formatDiagnostics(errors, host));
+ }
+ return options;
+}
+
+/**
+ * @param {any} e
+ * @returns {e is (OperationCanceledError | ts.OperationCanceledException)}
+ */
+function isCancellationError(e) {
+ return e instanceof OperationCanceledError ||
+ e instanceof ts.OperationCanceledException;
+}
+
+/**
+ * @param {number} _id
+ * @param {any} data
+ * @param {string | null} error
+ */
+// TODO(bartlomieju): this feels needlessly generic, both type checking
+// and language server use it with inefficient serialization. Id is not used
+// anyway...
+function respond(_id, data = null, error = null) {
+ if (error) {
+ ops.op_respond(
+ "error",
+ error,
+ );
+ } else {
+ ops.op_respond(JSON.stringify(data), "");
+ }
+}
+
+/** @typedef {[[string, number][], number, [string, any][]] } PendingChange */
+/**
+ * @template T
+ * @typedef {T | null} Option */
+
+/** @returns {Promise<[number, string, any[], string | null, Option] | null>} */
+async function pollRequests() {
+ return await ops.op_poll_requests();
+}
+
+let hasStarted = false;
+
+/** @param {boolean} enableDebugLogging */
+export async function serverMainLoop(enableDebugLogging) {
+ if (hasStarted) {
+ throw new Error("The language server has already been initialized.");
+ }
+ hasStarted = true;
+ LANGUAGE_SERVICE_ENTRIES.unscoped = {
+ ls: ts.createLanguageService(
+ host,
+ documentRegistry,
+ ),
+ compilerOptions: lspTsConfigToCompilerOptions({
+ "allowJs": true,
+ "esModuleInterop": true,
+ "experimentalDecorators": false,
+ "isolatedModules": true,
+ "lib": ["deno.ns", "deno.window", "deno.unstable"],
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "moduleDetection": "force",
+ "noEmit": true,
+ "noImplicitOverride": true,
+ "resolveJsonModule": true,
+ "strict": true,
+ "target": "esnext",
+ "useDefineForClassFields": true,
+ "jsx": "react",
+ "jsxFactory": "React.createElement",
+ "jsxFragmentFactory": "React.Fragment",
+ }),
+ };
+ setLogDebug(enableDebugLogging, "TSLS");
+ debug("serverInit()");
+
+ while (true) {
+ const request = await pollRequests();
+ if (request === null) {
+ break;
+ }
+ try {
+ serverRequest(
+ request[0],
+ request[1],
+ request[2],
+ request[3],
+ request[4],
+ );
+ } catch (err) {
+ error(`Internal error occurred processing request: ${err}`);
+ }
+ }
+}
+
+/**
+ * @param {any} error
+ * @param {any[] | null} args
+ */
+function formatErrorWithArgs(error, args) {
+ let errorString = "stack" in error
+ ? error.stack.toString()
+ : error.toString();
+ if (args) {
+ errorString += `\nFor request: [${
+ args.map((v) => JSON.stringify(v)).join(", ")
+ }]`;
+ }
+ return errorString;
+}
+
+/**
+ * @param {number} id
+ * @param {string} method
+ * @param {any[]} args
+ * @param {string | null} scope
+ * @param {PendingChange | null} maybeChange
+ */
+function serverRequest(id, method, args, scope, maybeChange) {
+ debug(`serverRequest()`, id, method, args, scope, maybeChange);
+ if (maybeChange !== null) {
+ const changedScripts = maybeChange[0];
+ const newProjectVersion = maybeChange[1];
+ const newConfigsByScope = maybeChange[2];
+ if (newConfigsByScope) {
+ IS_NODE_SOURCE_FILE_CACHE.clear();
+ ASSET_SCOPES.clear();
+ /** @type { typeof LANGUAGE_SERVICE_ENTRIES.byScope } */
+ const newByScope = new Map();
+ for (const [scope, config] of newConfigsByScope) {
+ LAST_REQUEST_SCOPE.set(scope);
+ const oldEntry = LANGUAGE_SERVICE_ENTRIES.byScope.get(scope);
+ const ls = oldEntry
+ ? oldEntry.ls
+ : ts.createLanguageService(host, documentRegistry);
+ const compilerOptions = lspTsConfigToCompilerOptions(config);
+ newByScope.set(scope, { ls, compilerOptions });
+ LANGUAGE_SERVICE_ENTRIES.byScope.delete(scope);
+ }
+ for (const oldEntry of LANGUAGE_SERVICE_ENTRIES.byScope.values()) {
+ oldEntry.ls.dispose();
+ }
+ LANGUAGE_SERVICE_ENTRIES.byScope = newByScope;
+ }
+
+ PROJECT_VERSION_CACHE.set(newProjectVersion);
+
+ let opened = false;
+ let closed = false;
+ for (const { 0: script, 1: changeKind } of changedScripts) {
+ if (changeKind === ChangeKind.Opened) {
+ opened = true;
+ } else if (changeKind === ChangeKind.Closed) {
+ closed = true;
+ }
+ SCRIPT_VERSION_CACHE.delete(script);
+ SCRIPT_SNAPSHOT_CACHE.delete(script);
+ }
+
+ if (newConfigsByScope || opened || closed) {
+ clearScriptNamesCache();
+ }
+ }
+
+ // For requests pertaining to an asset document, we make it so that the
+ // passed scope is just its own specifier. We map it to an actual scope here
+ // based on the first scope that the asset was loaded into.
+ if (scope?.startsWith(ASSETS_URL_PREFIX)) {
+ scope = ASSET_SCOPES.get(scope) ?? null;
+ }
+ LAST_REQUEST_METHOD.set(method);
+ LAST_REQUEST_SCOPE.set(scope);
+ const ls = (scope ? LANGUAGE_SERVICE_ENTRIES.byScope.get(scope)?.ls : null) ??
+ LANGUAGE_SERVICE_ENTRIES.unscoped.ls;
+ switch (method) {
+ case "$getSupportedCodeFixes": {
+ return respond(
+ id,
+ ts.getSupportedCodeFixes(),
+ );
+ }
+ case "$getAssets": {
+ return respond(id, getAssets());
+ }
+ case "$getDiagnostics": {
+ const projectVersion = args[1];
+ // there's a possibility that we receive a change notification
+ // but the diagnostic server queues a `$getDiagnostics` request
+ // with a stale project version. in that case, treat it as cancelled
+ // (it's about to be invalidated anyway).
+ const cachedProjectVersion = PROJECT_VERSION_CACHE.get();
+ if (cachedProjectVersion && projectVersion !== cachedProjectVersion) {
+ return respond(id, {});
+ }
+ try {
+ /** @type {Record} */
+ const diagnosticMap = {};
+ for (const specifier of args[0]) {
+ diagnosticMap[specifier] = fromTypeScriptDiagnostics([
+ ...ls.getSemanticDiagnostics(specifier),
+ ...ls.getSuggestionDiagnostics(specifier),
+ ...ls.getSyntacticDiagnostics(specifier),
+ ].filter(filterMapDiagnostic));
+ }
+ return respond(id, diagnosticMap);
+ } catch (e) {
+ if (
+ !isCancellationError(e)
+ ) {
+ return respond(
+ id,
+ {},
+ formatErrorWithArgs(e, [id, method, args, scope, maybeChange]),
+ );
+ }
+ return respond(id, {});
+ }
+ }
+ default:
+ if (typeof ls[method] === "function") {
+ // The `getCompletionEntryDetails()` method returns null if the
+ // `source` is `null` for whatever reason. It must be `undefined`.
+ if (method == "getCompletionEntryDetails") {
+ args[4] ??= undefined;
+ }
+ try {
+ return respond(id, ls[method](...args));
+ } catch (e) {
+ if (!isCancellationError(e)) {
+ return respond(
+ id,
+ null,
+ formatErrorWithArgs(e, [id, method, args, scope, maybeChange]),
+ );
+ }
+ return respond(id);
+ }
+ }
+ throw new TypeError(
+ // @ts-ignore exhausted case statement sets type to never
+ `Invalid request method for request: "${method}" (${id})`,
+ );
+ }
+}
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index 65319211fb..c99cfce9a2 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -12,1491 +12,263 @@
// https://tc39.es/ecma262/#sec-get-object.prototype.__proto__
delete Object.prototype.__proto__;
-((/** @type {any} */ window) => {
- /** @type {DenoCore} */
- const core = window.Deno.core;
- const ops = core.ops;
+import {
+ assert,
+ AssertionError,
+ ASSETS_URL_PREFIX,
+ debug,
+ filterMapDiagnostic,
+ fromTypeScriptDiagnostics,
+ getAssets,
+ host,
+ setLogDebug,
+ SOURCE_FILE_CACHE,
+} from "./97_ts_host.js";
+import { serverMainLoop } from "./98_lsp.js";
- let logDebug = false;
- let logSource = "JS";
+/** @type {DenoCore} */
+const core = globalThis.Deno.core;
+const ops = core.ops;
- // The map from the normalized specifier to the original.
- // TypeScript normalizes the specifier in its internal processing,
- // but the original specifier is needed when looking up the source from the runtime.
- // This map stores that relationship, and the original can be restored by the
- // normalized specifier.
- // See: https://github.com/denoland/deno/issues/9277#issuecomment-769653834
- /** @type {Map} */
- const normalizedToOriginalMap = new Map();
+// The map from the normalized specifier to the original.
+// TypeScript normalizes the specifier in its internal processing,
+// but the original specifier is needed when looking up the source from the runtime.
+// This map stores that relationship, and the original can be restored by the
+// normalized specifier.
+// See: https://github.com/denoland/deno/issues/9277#issuecomment-769653834
+/** @type {Map} */
+const normalizedToOriginalMap = new Map();
- /** @type {ReadonlySet} */
- const unstableDenoProps = new Set([
- "AtomicOperation",
- "DatagramConn",
- "Kv",
- "KvListIterator",
- "KvU64",
- "UnixConnectOptions",
- "UnixListenOptions",
- "listen",
- "listenDatagram",
- "openKv",
- "connectQuic",
- "listenQuic",
- "QuicBidirectionalStream",
- "QuicConn",
- "QuicListener",
- "QuicReceiveStream",
- "QuicSendStream",
- ]);
- const unstableMsgSuggestion =
- "If not, try changing the 'lib' compiler option to include 'deno.unstable' " +
- "or add a triple-slash directive to the top of your entrypoint (main file): " +
- '/// ';
+const SNAPSHOT_COMPILE_OPTIONS = {
+ esModuleInterop: true,
+ jsx: ts.JsxEmit.React,
+ module: ts.ModuleKind.ESNext,
+ noEmit: true,
+ strict: true,
+ target: ts.ScriptTarget.ESNext,
+ lib: ["lib.deno.window.d.ts"],
+};
- /**
- * @param {unknown} value
- * @returns {value is ts.CreateSourceFileOptions}
- */
- function isCreateSourceFileOptions(value) {
- return value != null && typeof value === "object" &&
- "languageVersion" in value;
+/** @type {Array<[string, number]>} */
+const stats = [];
+let statsStart = 0;
+
+function performanceStart() {
+ stats.length = 0;
+ statsStart = Date.now();
+ ts.performance.enable();
+}
+
+/**
+ * @param {{ program: ts.Program | ts.EmitAndSemanticDiagnosticsBuilderProgram, fileCount?: number }} options
+ */
+function performanceProgram({ program, fileCount }) {
+ if (program) {
+ if ("getProgram" in program) {
+ program = program.getProgram();
+ }
+ stats.push(["Files", program.getSourceFiles().length]);
+ stats.push(["Nodes", program.getNodeCount()]);
+ stats.push(["Identifiers", program.getIdentifierCount()]);
+ stats.push(["Symbols", program.getSymbolCount()]);
+ stats.push(["Types", program.getTypeCount()]);
+ stats.push(["Instantiations", program.getInstantiationCount()]);
+ } else if (fileCount != null) {
+ stats.push(["Files", fileCount]);
}
-
- /**
- * @param {ts.ScriptTarget | ts.CreateSourceFileOptions | undefined} versionOrOptions
- * @returns {ts.CreateSourceFileOptions}
- */
- function getCreateSourceFileOptions(versionOrOptions) {
- return isCreateSourceFileOptions(versionOrOptions)
- ? versionOrOptions
- : { languageVersion: versionOrOptions ?? ts.ScriptTarget.ESNext };
- }
-
- /**
- * @param debug {boolean}
- * @param source {string}
- */
- function setLogDebug(debug, source) {
- logDebug = debug;
- if (source) {
- logSource = source;
- }
- }
-
- /** @param msg {string} */
- function printStderr(msg) {
- core.print(msg, true);
- }
-
- /** @param args {any[]} */
- function debug(...args) {
- if (logDebug) {
- const stringifiedArgs = args.map((arg) =>
- typeof arg === "string" ? arg : JSON.stringify(arg)
- ).join(" ");
- printStderr(`DEBUG ${logSource} - ${stringifiedArgs}\n`);
- }
- }
-
- /** @param args {any[]} */
- function error(...args) {
- const stringifiedArgs = args.map((arg) =>
- typeof arg === "string" || arg instanceof Error
- ? String(arg)
- : JSON.stringify(arg)
- ).join(" ");
- printStderr(`ERROR ${logSource} = ${stringifiedArgs}\n`);
- }
-
- class AssertionError extends Error {
- /** @param msg {string} */
- constructor(msg) {
- super(msg);
- this.name = "AssertionError";
- }
- }
-
- /** @param cond {boolean} */
- function assert(cond, msg = "Assertion failed.") {
- if (!cond) {
- throw new AssertionError(msg);
- }
- }
-
- // In the case of the LSP, this will only ever contain the assets.
- /** @type {Map} */
- const sourceFileCache = new Map();
-
- /** @type {Map} */
- const scriptSnapshotCache = new Map();
-
- /** @type {Map} */
- const sourceRefCounts = new Map();
-
- /** @type {Map} */
- const scriptVersionCache = new Map();
-
- /** @type {Map} */
- const isNodeSourceFileCache = new Map();
-
- // Maps asset specifiers to the first scope that the asset was loaded into.
- /** @type {Map} */
- const assetScopes = new Map();
-
- /** @type {number | null} */
- let projectVersionCache = null;
-
- /** @type {string | null} */
- let lastRequestMethod = null;
-
- /** @type {string | null} */
- let lastRequestScope = null;
-
- const ChangeKind = {
- Opened: 0,
- Modified: 1,
- Closed: 2,
- };
-
- /**
- * @param {ts.CompilerOptions | ts.MinimalResolutionCacheHost} settingsOrHost
- * @returns {ts.CompilerOptions}
- */
- function getCompilationSettings(settingsOrHost) {
- if (typeof settingsOrHost.getCompilationSettings === "function") {
- return settingsOrHost.getCompilationSettings();
- }
- return /** @type {ts.CompilerOptions} */ (settingsOrHost);
- }
-
- // We need to use a custom document registry in order to provide source files
- // with an impliedNodeFormat to the ts language service
-
- /** @type {Map} */
- const documentRegistrySourceFileCache = new Map();
- const { getKeyForCompilationSettings } = ts.createDocumentRegistry(); // reuse this code
- /** @type {ts.DocumentRegistry} */
- const documentRegistry = {
- acquireDocument(
- fileName,
- compilationSettingsOrHost,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- ) {
- const key = getKeyForCompilationSettings(
- getCompilationSettings(compilationSettingsOrHost),
- );
- return this.acquireDocumentWithKey(
- fileName,
- /** @type {ts.Path} */ (fileName),
- compilationSettingsOrHost,
- key,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- );
- },
-
- acquireDocumentWithKey(
- fileName,
- path,
- _compilationSettingsOrHost,
- key,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- ) {
- const mapKey = path + key;
- let sourceFile = documentRegistrySourceFileCache.get(mapKey);
- if (!sourceFile || sourceFile.version !== version) {
- const isCjs = /** @type {any} */ (scriptSnapshot).isCjs;
- sourceFile = ts.createLanguageServiceSourceFile(
- fileName,
- scriptSnapshot,
- {
- ...getCreateSourceFileOptions(sourceFileOptions),
- impliedNodeFormat: isCjs
- ? ts.ModuleKind.CommonJS
- : ts.ModuleKind.ESNext,
- // in the lsp we want to be able to show documentation
- jsDocParsingMode: ts.JSDocParsingMode.ParseAll,
- },
- version,
- true,
- scriptKind,
- );
- documentRegistrySourceFileCache.set(mapKey, sourceFile);
- }
- const sourceRefCount = sourceRefCounts.get(fileName) ?? 0;
- sourceRefCounts.set(fileName, sourceRefCount + 1);
- return sourceFile;
- },
-
- updateDocument(
- fileName,
- compilationSettingsOrHost,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- ) {
- const key = getKeyForCompilationSettings(
- getCompilationSettings(compilationSettingsOrHost),
- );
- return this.updateDocumentWithKey(
- fileName,
- /** @type {ts.Path} */ (fileName),
- compilationSettingsOrHost,
- key,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- );
- },
-
- updateDocumentWithKey(
- fileName,
- path,
- compilationSettingsOrHost,
- key,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- ) {
- const mapKey = path + key;
- let sourceFile = documentRegistrySourceFileCache.get(mapKey) ??
- this.acquireDocumentWithKey(
- fileName,
- path,
- compilationSettingsOrHost,
- key,
- scriptSnapshot,
- version,
- scriptKind,
- sourceFileOptions,
- );
-
- if (sourceFile.version !== version) {
- sourceFile = ts.updateLanguageServiceSourceFile(
- sourceFile,
- scriptSnapshot,
- version,
- scriptSnapshot.getChangeRange(
- /** @type {ts.IScriptSnapshot} */ (sourceFile.scriptSnapShot),
- ),
- );
- documentRegistrySourceFileCache.set(mapKey, sourceFile);
- }
- return sourceFile;
- },
-
- getKeyForCompilationSettings(settings) {
- return getKeyForCompilationSettings(settings);
- },
-
- releaseDocument(
- fileName,
- compilationSettings,
- scriptKind,
- impliedNodeFormat,
- ) {
- const key = getKeyForCompilationSettings(compilationSettings);
- return this.releaseDocumentWithKey(
- /** @type {ts.Path} */ (fileName),
- key,
- scriptKind,
- impliedNodeFormat,
- );
- },
-
- releaseDocumentWithKey(path, key, _scriptKind, _impliedNodeFormat) {
- const sourceRefCount = sourceRefCounts.get(path) ?? 1;
- if (sourceRefCount <= 1) {
- sourceRefCounts.delete(path);
- // We call `cleanupSemanticCache` for other purposes, don't bust the
- // source cache in this case.
- if (lastRequestMethod != "cleanupSemanticCache") {
- const mapKey = path + key;
- documentRegistrySourceFileCache.delete(mapKey);
- scriptSnapshotCache.delete(path);
- ops.op_release(path);
- }
- } else {
- sourceRefCounts.set(path, sourceRefCount - 1);
- }
- },
-
- reportStats() {
- return "[]";
- },
- };
-
- ts.deno.setIsNodeSourceFileCallback((sourceFile) => {
- const fileName = sourceFile.fileName;
- let isNodeSourceFile = isNodeSourceFileCache.get(fileName);
- if (isNodeSourceFile == null) {
- const result = ops.op_is_node_file(fileName);
- isNodeSourceFile = /** @type {boolean} */ (result);
- isNodeSourceFileCache.set(fileName, isNodeSourceFile);
- }
- return isNodeSourceFile;
- });
-
- /**
- * @param msg {string}
- * @param code {number}
- */
- function formatMessage(msg, code) {
- switch (code) {
- case 2304: {
- if (msg === "Cannot find name 'Deno'.") {
- msg += " Do you need to change your target library? " +
- "Try changing the 'lib' compiler option to include 'deno.ns' " +
- "or add a triple-slash directive to the top of your entrypoint " +
- '(main file): /// ';
- }
- return msg;
- }
- case 2339: {
- const property = getProperty();
- if (property && unstableDenoProps.has(property)) {
- return `${msg} 'Deno.${property}' is an unstable API. ${unstableMsgSuggestion}`;
- }
- return msg;
- }
- default: {
- const property = getProperty();
- if (property && unstableDenoProps.has(property)) {
- const suggestion = getMsgSuggestion();
- if (suggestion) {
- return `${msg} 'Deno.${property}' is an unstable API. Did you mean '${suggestion}'? ${unstableMsgSuggestion}`;
- }
- }
- return msg;
- }
- }
-
- function getProperty() {
- return /Property '([^']+)' does not exist on type 'typeof Deno'/
- .exec(msg)?.[1];
- }
-
- function getMsgSuggestion() {
- return / Did you mean '([^']+)'\?/.exec(msg)?.[1];
- }
- }
-
- /** @param {ts.DiagnosticRelatedInformation} diagnostic */
- function fromRelatedInformation({
- start,
- length,
- file,
- messageText: msgText,
- ...ri
- }) {
- let messageText;
- let messageChain;
- if (typeof msgText === "object") {
- messageChain = msgText;
- } else {
- messageText = formatMessage(msgText, ri.code);
- }
- if (start !== undefined && length !== undefined && file) {
- let startPos = file.getLineAndCharacterOfPosition(start);
- let sourceLine = file.getFullText().split("\n")[startPos.line];
- const originalFileName = file.fileName;
- const fileName = ops.op_remap_specifier
- ? (ops.op_remap_specifier(file.fileName) ?? file.fileName)
- : file.fileName;
- // Bit of a hack to detect when we have a .wasm file and want to hide
- // the .d.ts text. This is not perfect, but will work in most scenarios
- if (
- fileName.endsWith(".wasm") && originalFileName.endsWith(".wasm.d.mts")
- ) {
- startPos = { line: 0, character: 0 };
- sourceLine = undefined;
- }
- return {
- start: startPos,
- end: file.getLineAndCharacterOfPosition(start + length),
- fileName,
- messageChain,
- messageText,
- sourceLine,
- ...ri,
- };
- } else {
- return {
- messageChain,
- messageText,
- ...ri,
- };
- }
- }
-
- /** @param {readonly ts.Diagnostic[]} diagnostics */
- function fromTypeScriptDiagnostics(diagnostics) {
- return diagnostics.map(({ relatedInformation: ri, source, ...diag }) => {
- /** @type {any} */
- const value = fromRelatedInformation(diag);
- value.relatedInformation = ri
- ? ri.map(fromRelatedInformation)
- : undefined;
- value.source = source;
- return value;
- });
- }
-
- // Using incremental compile APIs requires that all
- // paths must be either relative or absolute. Since
- // analysis in Rust operates on fully resolved URLs,
- // it makes sense to use the same scheme here.
- const ASSETS_URL_PREFIX = "asset:///";
- const CACHE_URL_PREFIX = "cache:///";
-
- /** Diagnostics that are intentionally ignored when compiling TypeScript in
- * Deno, as they provide misleading or incorrect information. */
- const IGNORED_DIAGNOSTICS = [
- // TS1452: 'resolution-mode' assertions are only supported when `moduleResolution` is `node16` or `nodenext`.
- // We specify the resolution mode to be CommonJS for some npm files and this
- // diagnostic gets generated even though we're using custom module resolution.
- 1452,
- // Module '...' cannot be imported using this construct. The specifier only resolves to an
- // ES module, which cannot be imported with 'require'.
- 1471,
- // TS1479: The current file is a CommonJS module whose imports will produce 'require' calls;
- // however, the referenced file is an ECMAScript module and cannot be imported with 'require'.
- 1479,
- // TS2306: File '.../index.d.ts' is not a module.
- // We get this for `x-typescript-types` declaration files which don't export
- // anything. We prefer to treat these as modules with no exports.
- 2306,
- // TS2688: Cannot find type definition file for '...'.
- // We ignore because type definition files can end with '.ts'.
- 2688,
- // TS2792: Cannot find module. Did you mean to set the 'moduleResolution'
- // option to 'node', or to add aliases to the 'paths' option?
- 2792,
- // TS2307: Cannot find module '{0}' or its corresponding type declarations.
- 2307,
- // Relative import errors to add an extension
- 2834,
- 2835,
- // TS5009: Cannot find the common subdirectory path for the input files.
- 5009,
- // TS5055: Cannot write file
- // 'http://localhost:4545/subdir/mt_application_x_javascript.j4.js'
- // because it would overwrite input file.
- 5055,
- // TypeScript is overly opinionated that only CommonJS modules kinds can
- // support JSON imports. Allegedly this was fixed in
- // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
- // so we will ignore complaints about this compiler setting.
- 5070,
- // TS6053: File '{0}' not found.
- 6053,
- // TS7016: Could not find a declaration file for module '...'. '...'
- // implicitly has an 'any' type. This is due to `allowJs` being off by
- // default but importing of a JavaScript module.
- 7016,
- ];
-
- const SNAPSHOT_COMPILE_OPTIONS = {
- esModuleInterop: true,
- jsx: ts.JsxEmit.React,
- module: ts.ModuleKind.ESNext,
- noEmit: true,
- strict: true,
- target: ts.ScriptTarget.ESNext,
- lib: ["lib.deno.window.d.ts"],
- };
-
- // todo(dsherret): can we remove this and just use ts.OperationCanceledException?
- /** Error thrown on cancellation. */
- class OperationCanceledError extends Error {
- }
-
- /**
- * This implementation calls into Rust to check if Tokio's cancellation token
- * has already been canceled.
- * @implements {ts.CancellationToken}
- */
- class CancellationToken {
- isCancellationRequested() {
- return ops.op_is_cancelled();
- }
-
- throwIfCancellationRequested() {
- if (this.isCancellationRequested()) {
- throw new OperationCanceledError();
- }
- }
- }
-
- /** @typedef {{
- * ls: ts.LanguageService & { [k:string]: any },
- * compilerOptions: ts.CompilerOptions,
- * }} LanguageServiceEntry */
- /** @type {{ unscoped: LanguageServiceEntry, byScope: Map }} */
- const languageServiceEntries = {
- // @ts-ignore Will be set later.
- unscoped: null,
- byScope: new Map(),
- };
-
- /** @type {{ unscoped: string[], byScope: Map } | null} */
- let scriptNamesCache = null;
-
- /** An object literal of the incremental compiler host, which provides the
- * specific "bindings" to the Deno environment that tsc needs to work.
- *
- * @type {ts.CompilerHost & ts.LanguageServiceHost} */
- const host = {
- fileExists(specifier) {
- if (logDebug) {
- debug(`host.fileExists("${specifier}")`);
- }
- // TODO(bartlomieju): is this assumption still valid?
- // this is used by typescript to find the libs path
- // so we can completely ignore it
- return false;
- },
- readFile(specifier) {
- if (logDebug) {
- debug(`host.readFile("${specifier}")`);
- }
- return ops.op_load(specifier)?.data;
- },
- getCancellationToken() {
- // createLanguageService will call this immediately and cache it
- return new CancellationToken();
- },
- getProjectVersion() {
- if (
- projectVersionCache
- ) {
- debug(`getProjectVersion cache hit : ${projectVersionCache}`);
- return projectVersionCache;
- }
- const projectVersion = ops.op_project_version();
- projectVersionCache = projectVersion;
- debug(`getProjectVersion cache miss : ${projectVersionCache}`);
- return projectVersion;
- },
- // @ts-ignore Undocumented method.
- getModuleSpecifierCache() {
- return moduleSpecifierCache;
- },
- // @ts-ignore Undocumented method.
- getCachedExportInfoMap() {
- return exportMapCache;
- },
- getGlobalTypingsCacheLocation() {
- return undefined;
- },
- // @ts-ignore Undocumented method.
- toPath(fileName) {
- // @ts-ignore Undocumented function.
- ts.toPath(
- fileName,
- this.getCurrentDirectory(),
- this.getCanonicalFileName.bind(this),
- );
- },
- // @ts-ignore Undocumented method.
- watchNodeModulesForPackageJsonChanges() {
- return { close() {} };
- },
- getSourceFile(
- specifier,
- languageVersion,
- _onError,
- // this is not used by the lsp because source
- // files are created in the document registry
- _shouldCreateNewSourceFile,
- ) {
- if (logDebug) {
- debug(
- `host.getSourceFile("${specifier}", ${
- ts.ScriptTarget[
- getCreateSourceFileOptions(languageVersion).languageVersion
- ]
- })`,
- );
- }
-
- // Needs the original specifier
- specifier = normalizedToOriginalMap.get(specifier) ?? specifier;
-
- let sourceFile = sourceFileCache.get(specifier);
- if (sourceFile) {
- return sourceFile;
- }
-
- /** @type {{ data: string; scriptKind: ts.ScriptKind; version: string; isCjs: boolean }} */
- const fileInfo = ops.op_load(specifier);
- if (!fileInfo) {
- return undefined;
- }
- const { data, scriptKind, version, isCjs } = fileInfo;
- assert(
- data != null,
- `"data" is unexpectedly null for "${specifier}".`,
- );
-
- sourceFile = ts.createSourceFile(
- specifier,
- data,
- {
- ...getCreateSourceFileOptions(languageVersion),
- impliedNodeFormat: isCjs
- ? ts.ModuleKind.CommonJS
- : ts.ModuleKind.ESNext,
- // no need to parse docs for `deno check`
- jsDocParsingMode: ts.JSDocParsingMode.ParseForTypeErrors,
- },
- false,
- scriptKind,
- );
- sourceFile.moduleName = specifier;
- sourceFile.version = version;
- if (specifier.startsWith(ASSETS_URL_PREFIX)) {
- sourceFile.version = "1";
- }
- sourceFileCache.set(specifier, sourceFile);
- scriptVersionCache.set(specifier, version);
- return sourceFile;
- },
- getDefaultLibFileName() {
- return `${ASSETS_URL_PREFIX}lib.esnext.d.ts`;
- },
- getDefaultLibLocation() {
- return ASSETS_URL_PREFIX;
- },
- writeFile(fileName, data, _writeByteOrderMark, _onError, _sourceFiles) {
- if (logDebug) {
- debug(`host.writeFile("${fileName}")`);
- }
- return ops.op_emit(
- data,
- fileName,
- );
- },
- getCurrentDirectory() {
- if (logDebug) {
- debug(`host.getCurrentDirectory()`);
- }
- return CACHE_URL_PREFIX;
- },
- getCanonicalFileName(fileName) {
- return fileName;
- },
- useCaseSensitiveFileNames() {
- return true;
- },
- getNewLine() {
- return "\n";
- },
- resolveTypeReferenceDirectiveReferences(
- typeDirectiveReferences,
- containingFilePath,
- _redirectedReference,
- options,
- containingSourceFile,
- _reusedNames,
- ) {
- const isCjs =
- containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS;
- const toResolve = typeDirectiveReferences.map((arg) => {
- /** @type {ts.FileReference} */
- const fileReference = typeof arg === "string"
- ? {
- pos: -1,
- end: -1,
- fileName: arg,
- }
- : arg;
- return [
- fileReference.resolutionMode == null
- ? isCjs
- : fileReference.resolutionMode === ts.ModuleKind.CommonJS,
- fileReference.fileName,
- ];
- });
-
- /** @type {Array<[string, ts.Extension | null] | undefined>} */
- const resolved = ops.op_resolve(
- containingFilePath,
- toResolve,
- );
-
- /** @type {Array} */
- const result = resolved.map((item) => {
- if (item && item[1]) {
- const [resolvedFileName, extension] = item;
- return {
- resolvedTypeReferenceDirective: {
- primary: true,
- resolvedFileName,
- extension,
- isExternalLibraryImport: false,
- },
- };
- } else {
- return {
- resolvedTypeReferenceDirective: undefined,
- };
- }
- });
-
- if (logDebug) {
- debug(
- "resolveTypeReferenceDirectiveReferences ",
- typeDirectiveReferences,
- containingFilePath,
- options,
- containingSourceFile?.fileName,
- " => ",
- result,
- );
- }
- return result;
- },
- resolveModuleNameLiterals(
- moduleLiterals,
- base,
- _redirectedReference,
- compilerOptions,
- containingSourceFile,
- _reusedNames,
- ) {
- const specifiers = moduleLiterals.map((literal) => [
- ts.getModeForUsageLocation(
- containingSourceFile,
- literal,
- compilerOptions,
- ) === ts.ModuleKind.CommonJS,
- literal.text,
- ]);
- if (logDebug) {
- debug(`host.resolveModuleNames()`);
- debug(` base: ${base}`);
- debug(` specifiers: ${specifiers.map((s) => s[1]).join(", ")}`);
- }
- /** @type {Array<[string, ts.Extension | null] | undefined>} */
- const resolved = ops.op_resolve(
- base,
- specifiers,
- );
- if (resolved) {
- /** @type {Array} */
- const result = resolved.map((item) => {
- if (item && item[1]) {
- const [resolvedFileName, extension] = item;
- return {
- resolvedModule: {
- resolvedFileName,
- extension,
- // todo(dsherret): we should probably be setting this
- isExternalLibraryImport: false,
- },
- };
- }
- return {
- resolvedModule: undefined,
- };
- });
- result.length = specifiers.length;
- return result;
- } else {
- return new Array(specifiers.length);
- }
- },
- createHash(data) {
- return ops.op_create_hash(data);
- },
-
- // LanguageServiceHost
- getCompilationSettings() {
- if (logDebug) {
- debug("host.getCompilationSettings()");
- }
- return (lastRequestScope
- ? languageServiceEntries.byScope.get(lastRequestScope)?.compilerOptions
- : null) ?? languageServiceEntries.unscoped.compilerOptions;
- },
- getScriptFileNames() {
- if (logDebug) {
- debug("host.getScriptFileNames()");
- }
- if (!scriptNamesCache) {
- const { unscoped, byScope } = ops.op_script_names();
- scriptNamesCache = {
- unscoped,
- byScope: new Map(Object.entries(byScope)),
- };
- }
- return (lastRequestScope
- ? scriptNamesCache.byScope.get(lastRequestScope)
- : null) ?? scriptNamesCache.unscoped;
- },
- getScriptVersion(specifier) {
- if (logDebug) {
- debug(`host.getScriptVersion("${specifier}")`);
- }
- if (specifier.startsWith(ASSETS_URL_PREFIX)) {
- return "1";
- }
- // tsc requests the script version multiple times even though it can't
- // possibly have changed, so we will memoize it on a per request basis.
- if (scriptVersionCache.has(specifier)) {
- return scriptVersionCache.get(specifier);
- }
- const scriptVersion = ops.op_script_version(specifier);
- scriptVersionCache.set(specifier, scriptVersion);
- return scriptVersion;
- },
- getScriptSnapshot(specifier) {
- if (logDebug) {
- debug(`host.getScriptSnapshot("${specifier}")`);
- }
- if (specifier.startsWith(ASSETS_URL_PREFIX)) {
- const sourceFile = this.getSourceFile(
- specifier,
- ts.ScriptTarget.ESNext,
- );
- if (sourceFile) {
- if (!assetScopes.has(specifier)) {
- assetScopes.set(specifier, lastRequestScope);
- }
- // This case only occurs for assets.
- return ts.ScriptSnapshot.fromString(sourceFile.text);
- }
- }
- let scriptSnapshot = scriptSnapshotCache.get(specifier);
- if (scriptSnapshot == undefined) {
- /** @type {{ data: string, version: string, isCjs: boolean }} */
- const fileInfo = ops.op_load(specifier);
- if (!fileInfo) {
- return undefined;
- }
- scriptSnapshot = ts.ScriptSnapshot.fromString(fileInfo.data);
- scriptSnapshot.isCjs = fileInfo.isCjs;
- scriptSnapshotCache.set(specifier, scriptSnapshot);
- scriptVersionCache.set(specifier, fileInfo.version);
- }
- return scriptSnapshot;
- },
- };
-
- // @ts-ignore Undocumented function.
- const moduleSpecifierCache = ts.server.createModuleSpecifierCache(host);
-
- // @ts-ignore Undocumented function.
- const exportMapCache = ts.createCacheableExportInfoMap(host);
-
- // override the npm install @types package diagnostics to be deno specific
- ts.setLocalizedDiagnosticMessages((() => {
- const nodeMessage = "Cannot find name '{0}'."; // don't offer any suggestions
- const jqueryMessage =
- "Cannot find name '{0}'. Did you mean to import jQuery? Try adding `import $ from \"npm:jquery\";`.";
- return {
- "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashno_2580":
- nodeMessage,
- "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashno_2591":
- nodeMessage,
- "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slash_2581":
- jqueryMessage,
- "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slash_2592":
- jqueryMessage,
- "Module_0_was_resolved_to_1_but_allowArbitraryExtensions_is_not_set_6263":
- "Module '{0}' was resolved to '{1}', but importing these modules is not supported.",
- };
- })());
-
- /** @type {Array<[string, number]>} */
- const stats = [];
- let statsStart = 0;
-
- function performanceStart() {
- stats.length = 0;
- statsStart = Date.now();
- ts.performance.enable();
- }
-
- /**
- * @param {{ program: ts.Program | ts.EmitAndSemanticDiagnosticsBuilderProgram, fileCount?: number }} options
- */
- function performanceProgram({ program, fileCount }) {
- if (program) {
- if ("getProgram" in program) {
- program = program.getProgram();
- }
- stats.push(["Files", program.getSourceFiles().length]);
- stats.push(["Nodes", program.getNodeCount()]);
- stats.push(["Identifiers", program.getIdentifierCount()]);
- stats.push(["Symbols", program.getSymbolCount()]);
- stats.push(["Types", program.getTypeCount()]);
- stats.push(["Instantiations", program.getInstantiationCount()]);
- } else if (fileCount != null) {
- stats.push(["Files", fileCount]);
- }
- const programTime = ts.performance.getDuration("Program");
- const bindTime = ts.performance.getDuration("Bind");
- const checkTime = ts.performance.getDuration("Check");
- const emitTime = ts.performance.getDuration("Emit");
- stats.push(["Parse time", programTime]);
- stats.push(["Bind time", bindTime]);
- stats.push(["Check time", checkTime]);
- stats.push(["Emit time", emitTime]);
- stats.push(
- ["Total TS time", programTime + bindTime + checkTime + emitTime],
- );
- }
-
- function performanceEnd() {
- const duration = Date.now() - statsStart;
- stats.push(["Compile time", duration]);
- return stats;
- }
-
- /**
- * @typedef {object} Request
- * @property {Record} config
- * @property {boolean} debug
- * @property {string[]} rootNames
- * @property {boolean} localOnly
- */
-
- /**
- * Checks the normalized version of the root name and stores it in
- * `normalizedToOriginalMap`. If the normalized specifier is already
- * registered for the different root name, it throws an AssertionError.
- *
- * @param {string} rootName
- */
- function checkNormalizedPath(rootName) {
- const normalized = ts.normalizePath(rootName);
- const originalRootName = normalizedToOriginalMap.get(normalized);
- if (typeof originalRootName === "undefined") {
- normalizedToOriginalMap.set(normalized, rootName);
- } else if (originalRootName !== rootName) {
- // The different root names are normalizd to the same path.
- // This will cause problem when looking up the source for each.
- throw new AssertionError(
- `The different names for the same normalized specifier are specified: normalized=${normalized}, rootNames=${originalRootName},${rootName}`,
- );
- }
- }
-
- /** @param {Record} config */
- function normalizeConfig(config) {
- // the typescript compiler doesn't know about the precompile
- // transform at the moment, so just tell it we're using react-jsx
- if (config.jsx === "precompile") {
- config.jsx = "react-jsx";
- }
- if (config.jsxPrecompileSkipElements) {
- delete config.jsxPrecompileSkipElements;
- }
- return config;
- }
-
- /** @param {Record} config */
- function lspTsConfigToCompilerOptions(config) {
- const normalizedConfig = normalizeConfig(config);
- const { options, errors } = ts
- .convertCompilerOptionsFromJson(normalizedConfig, "");
- Object.assign(options, {
- allowNonTsExtensions: true,
- allowImportingTsExtensions: true,
- module: ts.ModuleKind.NodeNext,
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
- });
- if (errors.length > 0 && logDebug) {
- debug(ts.formatDiagnostics(errors, host));
- }
- return options;
- }
-
- /** The API that is called by Rust when executing a request.
- * @param {Request} request
- */
- function exec({ config, debug: debugFlag, rootNames, localOnly }) {
- setLogDebug(debugFlag, "TS");
- performanceStart();
-
- config = normalizeConfig(config);
-
- if (logDebug) {
- debug(">>> exec start", { rootNames });
- debug(config);
- }
-
- rootNames.forEach(checkNormalizedPath);
-
- const { options, errors: configFileParsingDiagnostics } = ts
- .convertCompilerOptionsFromJson(config, "");
- // The `allowNonTsExtensions` is a "hidden" compiler option used in VSCode
- // which is not allowed to be passed in JSON, we need it to allow special
- // URLs which Deno supports. So we need to either ignore the diagnostic, or
- // inject it ourselves.
- Object.assign(options, { allowNonTsExtensions: true });
- const program = ts.createIncrementalProgram({
- rootNames,
- options,
- host,
- configFileParsingDiagnostics,
- });
-
- let checkFiles = undefined;
-
- if (localOnly) {
- const checkFileNames = new Set();
- checkFiles = [];
-
- for (const checkName of rootNames) {
- if (checkName.startsWith("http")) {
- continue;
- }
- const sourceFile = program.getSourceFile(checkName);
- if (sourceFile != null) {
- checkFiles.push(sourceFile);
- }
- checkFileNames.add(checkName);
- }
-
- // When calling program.getSemanticDiagnostics(...) with a source file, we
- // need to call this code first in order to get it to invalidate cached
- // diagnostics correctly. This is what program.getSemanticDiagnostics()
- // does internally when calling without any arguments.
- while (
- program.getSemanticDiagnosticsOfNextAffectedFile(
- undefined,
- /* ignoreSourceFile */ (s) => !checkFileNames.has(s.fileName),
- )
- ) {
- // keep going until there are no more affected files
- }
- }
-
- const diagnostics = [
- ...program.getConfigFileParsingDiagnostics(),
- ...(checkFiles == null
- ? program.getSyntacticDiagnostics()
- : ts.sortAndDeduplicateDiagnostics(
- checkFiles.map((s) => program.getSyntacticDiagnostics(s)).flat(),
- )),
- ...program.getOptionsDiagnostics(),
- ...program.getGlobalDiagnostics(),
- ...(checkFiles == null
- ? program.getSemanticDiagnostics()
- : ts.sortAndDeduplicateDiagnostics(
- checkFiles.map((s) => program.getSemanticDiagnostics(s)).flat(),
- )),
- ].filter(filterMapDiagnostic);
-
- // emit the tsbuildinfo file
- // @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871)
- program.emitBuildInfo(host.writeFile);
-
- performanceProgram({ program });
-
- ops.op_respond({
- diagnostics: fromTypeScriptDiagnostics(diagnostics),
- stats: performanceEnd(),
- });
- debug("<<< exec stop");
- }
-
- /** @param {ts.Diagnostic} diagnostic */
- function filterMapDiagnostic(diagnostic) {
- if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) {
- return false;
- }
-
- // ignore diagnostics resulting from the `ImportMeta` declaration in deno merging with
- // the one in @types/node. the types of the filename and dirname properties are different,
- // which causes tsc to error.
- const importMetaFilenameDirnameModifiersRe =
- /^All declarations of '(filename|dirname)'/;
- const importMetaFilenameDirnameTypesRe =
- /^Subsequent property declarations must have the same type.\s+Property '(filename|dirname)'/;
- // Declarations of X must have identical modifiers.
- if (diagnostic.code === 2687) {
- if (
- typeof diagnostic.messageText === "string" &&
- (importMetaFilenameDirnameModifiersRe.test(diagnostic.messageText)) &&
- (diagnostic.file?.fileName.startsWith("asset:///") ||
- diagnostic.file?.fileName?.includes("@types/node"))
- ) {
- return false;
- }
- }
- // Subsequent property declarations must have the same type.
- if (diagnostic.code === 2717) {
- if (
- typeof diagnostic.messageText === "string" &&
- (importMetaFilenameDirnameTypesRe.test(diagnostic.messageText)) &&
- (diagnostic.file?.fileName.startsWith("asset:///") ||
- diagnostic.file?.fileName?.includes("@types/node"))
- ) {
- return false;
- }
- }
- // make the diagnostic for using an `export =` in an es module a warning
- if (diagnostic.code === 1203) {
- diagnostic.category = ts.DiagnosticCategory.Warning;
- if (typeof diagnostic.messageText === "string") {
- const message =
- " This will start erroring in a future version of Deno 2 " +
- "in order to align with TypeScript.";
- // seems typescript shares objects, so check if it's already been set
- if (!diagnostic.messageText.endsWith(message)) {
- diagnostic.messageText += message;
- }
- }
- }
- return true;
- }
-
- /**
- * @param {any} e
- * @returns {e is (OperationCanceledError | ts.OperationCanceledException)}
- */
- function isCancellationError(e) {
- return e instanceof OperationCanceledError ||
- e instanceof ts.OperationCanceledException;
- }
-
- function getAssets() {
- /** @type {{ specifier: string; text: string; }[]} */
- const assets = [];
- for (const sourceFile of sourceFileCache.values()) {
- if (sourceFile.fileName.startsWith(ASSETS_URL_PREFIX)) {
- assets.push({
- specifier: sourceFile.fileName,
- text: sourceFile.text,
- });
- }
- }
- return assets;
- }
-
- /**
- * @param {number} _id
- * @param {any} data
- * @param {string | null} error
- */
- // TODO(bartlomieju): this feels needlessly generic, both type checking
- // and language server use it with inefficient serialization. Id is not used
- // anyway...
- function respond(_id, data = null, error = null) {
- if (error) {
- ops.op_respond(
- "error",
- error,
- );
- } else {
- ops.op_respond(JSON.stringify(data), "");
- }
- }
-
- /** @typedef {[[string, number][], number, [string, any][]] } PendingChange */
- /**
- * @template T
- * @typedef {T | null} Option */
-
- /** @returns {Promise<[number, string, any[], string | null, Option] | null>} */
- async function pollRequests() {
- return await ops.op_poll_requests();
- }
-
- let hasStarted = false;
-
- /** @param {boolean} enableDebugLogging */
- async function serverMainLoop(enableDebugLogging) {
- if (hasStarted) {
- throw new Error("The language server has already been initialized.");
- }
- hasStarted = true;
- languageServiceEntries.unscoped = {
- ls: ts.createLanguageService(
- host,
- documentRegistry,
- ),
- compilerOptions: lspTsConfigToCompilerOptions({
- "allowJs": true,
- "esModuleInterop": true,
- "experimentalDecorators": false,
- "isolatedModules": true,
- "lib": ["deno.ns", "deno.window", "deno.unstable"],
- "module": "NodeNext",
- "moduleResolution": "NodeNext",
- "moduleDetection": "force",
- "noEmit": true,
- "noImplicitOverride": true,
- "resolveJsonModule": true,
- "strict": true,
- "target": "esnext",
- "useDefineForClassFields": true,
- "jsx": "react",
- "jsxFactory": "React.createElement",
- "jsxFragmentFactory": "React.Fragment",
- }),
- };
- setLogDebug(enableDebugLogging, "TSLS");
- debug("serverInit()");
-
- while (true) {
- const request = await pollRequests();
- if (request === null) {
- break;
- }
- try {
- serverRequest(
- request[0],
- request[1],
- request[2],
- request[3],
- request[4],
- );
- } catch (err) {
- error(`Internal error occurred processing request: ${err}`);
- }
- }
- }
-
- /**
- * @param {any} error
- * @param {any[] | null} args
- */
- function formatErrorWithArgs(error, args) {
- let errorString = "stack" in error
- ? error.stack.toString()
- : error.toString();
- if (args) {
- errorString += `\nFor request: [${
- args.map((v) => JSON.stringify(v)).join(", ")
- }]`;
- }
- return errorString;
- }
-
- /**
- * @param {number} id
- * @param {string} method
- * @param {any[]} args
- * @param {string | null} scope
- * @param {PendingChange | null} maybeChange
- */
- function serverRequest(id, method, args, scope, maybeChange) {
- if (logDebug) {
- debug(`serverRequest()`, id, method, args, scope, maybeChange);
- }
- if (maybeChange !== null) {
- const changedScripts = maybeChange[0];
- const newProjectVersion = maybeChange[1];
- const newConfigsByScope = maybeChange[2];
- if (newConfigsByScope) {
- isNodeSourceFileCache.clear();
- assetScopes.clear();
- /** @type { typeof languageServiceEntries.byScope } */
- const newByScope = new Map();
- for (const [scope, config] of newConfigsByScope) {
- lastRequestScope = scope;
- const oldEntry = languageServiceEntries.byScope.get(scope);
- const ls = oldEntry
- ? oldEntry.ls
- : ts.createLanguageService(host, documentRegistry);
- const compilerOptions = lspTsConfigToCompilerOptions(config);
- newByScope.set(scope, { ls, compilerOptions });
- languageServiceEntries.byScope.delete(scope);
- }
- for (const oldEntry of languageServiceEntries.byScope.values()) {
- oldEntry.ls.dispose();
- }
- languageServiceEntries.byScope = newByScope;
- }
-
- projectVersionCache = newProjectVersion;
-
- let opened = false;
- let closed = false;
- for (const { 0: script, 1: changeKind } of changedScripts) {
- if (changeKind === ChangeKind.Opened) {
- opened = true;
- } else if (changeKind === ChangeKind.Closed) {
- closed = true;
- }
- scriptVersionCache.delete(script);
- scriptSnapshotCache.delete(script);
- }
-
- if (newConfigsByScope || opened || closed) {
- scriptNamesCache = null;
- }
- }
-
- // For requests pertaining to an asset document, we make it so that the
- // passed scope is just its own specifier. We map it to an actual scope here
- // based on the first scope that the asset was loaded into.
- if (scope?.startsWith(ASSETS_URL_PREFIX)) {
- scope = assetScopes.get(scope) ?? null;
- }
- lastRequestMethod = method;
- lastRequestScope = scope;
- const ls = (scope ? languageServiceEntries.byScope.get(scope)?.ls : null) ??
- languageServiceEntries.unscoped.ls;
- switch (method) {
- case "$getSupportedCodeFixes": {
- return respond(
- id,
- ts.getSupportedCodeFixes(),
- );
- }
- case "$getAssets": {
- return respond(id, getAssets());
- }
- case "$getDiagnostics": {
- const projectVersion = args[1];
- // there's a possibility that we receive a change notification
- // but the diagnostic server queues a `$getDiagnostics` request
- // with a stale project version. in that case, treat it as cancelled
- // (it's about to be invalidated anyway).
- if (projectVersionCache && projectVersion !== projectVersionCache) {
- return respond(id, {});
- }
- try {
- /** @type {Record} */
- const diagnosticMap = {};
- for (const specifier of args[0]) {
- diagnosticMap[specifier] = fromTypeScriptDiagnostics([
- ...ls.getSemanticDiagnostics(specifier),
- ...ls.getSuggestionDiagnostics(specifier),
- ...ls.getSyntacticDiagnostics(specifier),
- ].filter(filterMapDiagnostic));
- }
- return respond(id, diagnosticMap);
- } catch (e) {
- if (
- !isCancellationError(e)
- ) {
- return respond(
- id,
- {},
- formatErrorWithArgs(e, [id, method, args, scope, maybeChange]),
- );
- }
- return respond(id, {});
- }
- }
- default:
- if (typeof ls[method] === "function") {
- // The `getCompletionEntryDetails()` method returns null if the
- // `source` is `null` for whatever reason. It must be `undefined`.
- if (method == "getCompletionEntryDetails") {
- args[4] ??= undefined;
- }
- try {
- return respond(id, ls[method](...args));
- } catch (e) {
- if (!isCancellationError(e)) {
- return respond(
- id,
- null,
- formatErrorWithArgs(e, [id, method, args, scope, maybeChange]),
- );
- }
- return respond(id);
- }
- }
- throw new TypeError(
- // @ts-ignore exhausted case statement sets type to never
- `Invalid request method for request: "${method}" (${id})`,
- );
- }
- }
-
- // A build time only op that provides some setup information that is used to
- // ensure the snapshot is setup properly.
- /** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
- const { buildSpecifier, libs } = ops.op_build_info();
-
- // list of globals that should be kept in Node's globalThis
- ts.deno.setNodeOnlyGlobalNames([
- "__dirname",
- "__filename",
- "Buffer",
- "BufferConstructor",
- "BufferEncoding",
- "clearImmediate",
- "clearInterval",
- "clearTimeout",
- "console",
- "Console",
- "ErrorConstructor",
- "gc",
- "Global",
- "localStorage",
- "queueMicrotask",
- "RequestInit",
- "ResponseInit",
- "sessionStorage",
- "setImmediate",
- "setInterval",
- "setTimeout",
- ]);
-
- for (const lib of libs) {
- const specifier = `lib.${lib}.d.ts`;
- // we are using internal APIs here to "inject" our custom libraries into
- // tsc, so things like `"lib": [ "deno.ns" ]` are supported.
- if (!ts.libs.includes(lib)) {
- ts.libs.push(lib);
- ts.libMap.set(lib, `lib.${lib}.d.ts`);
- }
- // we are caching in memory common type libraries that will be re-used by
- // tsc on when the snapshot is restored
- assert(
- !!host.getSourceFile(
- `${ASSETS_URL_PREFIX}${specifier}`,
- ts.ScriptTarget.ESNext,
- ),
- `failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
- );
- }
- // this helps ensure as much as possible is in memory that is re-usable
- // before the snapshotting is done, which helps unsure fast "startup" for
- // subsequent uses of tsc in Deno.
- const TS_SNAPSHOT_PROGRAM = ts.createProgram({
- rootNames: [buildSpecifier],
- options: SNAPSHOT_COMPILE_OPTIONS,
- host,
- });
- assert(
- ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0,
- "lib.d.ts files have errors",
+ const programTime = ts.performance.getDuration("Program");
+ const bindTime = ts.performance.getDuration("Bind");
+ const checkTime = ts.performance.getDuration("Check");
+ const emitTime = ts.performance.getDuration("Emit");
+ stats.push(["Parse time", programTime]);
+ stats.push(["Bind time", bindTime]);
+ stats.push(["Check time", checkTime]);
+ stats.push(["Emit time", emitTime]);
+ stats.push(
+ ["Total TS time", programTime + bindTime + checkTime + emitTime],
);
+}
- // remove this now that we don't need it anymore for warming up tsc
- sourceFileCache.delete(buildSpecifier);
+function performanceEnd() {
+ const duration = Date.now() - statsStart;
+ stats.push(["Compile time", duration]);
+ return stats;
+}
- // exposes the functions that are called by `tsc::exec()` when type
- // checking TypeScript.
- /** @type {any} */
- const global = globalThis;
- global.exec = exec;
- global.getAssets = getAssets;
+/**
+ * @typedef {object} Request
+ * @property {Record} config
+ * @property {boolean} debug
+ * @property {string[]} rootNames
+ * @property {boolean} localOnly
+ */
- // exposes the functions that are called when the compiler is used as a
- // language service.
- global.serverMainLoop = serverMainLoop;
-})(this);
+/**
+ * Checks the normalized version of the root name and stores it in
+ * `normalizedToOriginalMap`. If the normalized specifier is already
+ * registered for the different root name, it throws an AssertionError.
+ *
+ * @param {string} rootName
+ */
+function checkNormalizedPath(rootName) {
+ const normalized = ts.normalizePath(rootName);
+ const originalRootName = normalizedToOriginalMap.get(normalized);
+ if (typeof originalRootName === "undefined") {
+ normalizedToOriginalMap.set(normalized, rootName);
+ } else if (originalRootName !== rootName) {
+ // The different root names are normalizd to the same path.
+ // This will cause problem when looking up the source for each.
+ throw new AssertionError(
+ `The different names for the same normalized specifier are specified: normalized=${normalized}, rootNames=${originalRootName},${rootName}`,
+ );
+ }
+}
+
+/** @param {Record} config */
+function normalizeConfig(config) {
+ // the typescript compiler doesn't know about the precompile
+ // transform at the moment, so just tell it we're using react-jsx
+ if (config.jsx === "precompile") {
+ config.jsx = "react-jsx";
+ }
+ if (config.jsxPrecompileSkipElements) {
+ delete config.jsxPrecompileSkipElements;
+ }
+ return config;
+}
+
+/** The API that is called by Rust when executing a request.
+ * @param {Request} request
+ */
+function exec({ config, debug: debugFlag, rootNames, localOnly }) {
+ setLogDebug(debugFlag, "TS");
+ performanceStart();
+
+ config = normalizeConfig(config);
+
+ debug(">>> exec start", { rootNames });
+ debug(config);
+
+ rootNames.forEach(checkNormalizedPath);
+
+ const { options, errors: configFileParsingDiagnostics } = ts
+ .convertCompilerOptionsFromJson(config, "");
+ // The `allowNonTsExtensions` is a "hidden" compiler option used in VSCode
+ // which is not allowed to be passed in JSON, we need it to allow special
+ // URLs which Deno supports. So we need to either ignore the diagnostic, or
+ // inject it ourselves.
+ Object.assign(options, { allowNonTsExtensions: true });
+ const program = ts.createIncrementalProgram({
+ rootNames,
+ options,
+ host,
+ configFileParsingDiagnostics,
+ });
+
+ let checkFiles = undefined;
+
+ if (localOnly) {
+ const checkFileNames = new Set();
+ checkFiles = [];
+
+ for (const checkName of rootNames) {
+ if (checkName.startsWith("http")) {
+ continue;
+ }
+ const sourceFile = program.getSourceFile(checkName);
+ if (sourceFile != null) {
+ checkFiles.push(sourceFile);
+ }
+ checkFileNames.add(checkName);
+ }
+
+ // When calling program.getSemanticDiagnostics(...) with a source file, we
+ // need to call this code first in order to get it to invalidate cached
+ // diagnostics correctly. This is what program.getSemanticDiagnostics()
+ // does internally when calling without any arguments.
+ while (
+ program.getSemanticDiagnosticsOfNextAffectedFile(
+ undefined,
+ /* ignoreSourceFile */ (s) => !checkFileNames.has(s.fileName),
+ )
+ ) {
+ // keep going until there are no more affected files
+ }
+ }
+
+ const diagnostics = [
+ ...program.getConfigFileParsingDiagnostics(),
+ ...(checkFiles == null
+ ? program.getSyntacticDiagnostics()
+ : ts.sortAndDeduplicateDiagnostics(
+ checkFiles.map((s) => program.getSyntacticDiagnostics(s)).flat(),
+ )),
+ ...program.getOptionsDiagnostics(),
+ ...program.getGlobalDiagnostics(),
+ ...(checkFiles == null
+ ? program.getSemanticDiagnostics()
+ : ts.sortAndDeduplicateDiagnostics(
+ checkFiles.map((s) => program.getSemanticDiagnostics(s)).flat(),
+ )),
+ ].filter(filterMapDiagnostic);
+
+ // emit the tsbuildinfo file
+ // @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871)
+ program.emitBuildInfo(host.writeFile);
+
+ performanceProgram({ program });
+
+ ops.op_respond({
+ diagnostics: fromTypeScriptDiagnostics(diagnostics),
+ stats: performanceEnd(),
+ });
+ debug("<<< exec stop");
+}
+
+// A build time only op that provides some setup information that is used to
+// ensure the snapshot is setup properly.
+/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
+const { buildSpecifier, libs } = ops.op_build_info();
+
+for (const lib of libs) {
+ const specifier = `lib.${lib}.d.ts`;
+ // we are using internal APIs here to "inject" our custom libraries into
+ // tsc, so things like `"lib": [ "deno.ns" ]` are supported.
+ if (!ts.libs.includes(lib)) {
+ ts.libs.push(lib);
+ ts.libMap.set(lib, `lib.${lib}.d.ts`);
+ }
+ // we are caching in memory common type libraries that will be re-used by
+ // tsc on when the snapshot is restored
+ assert(
+ !!host.getSourceFile(
+ `${ASSETS_URL_PREFIX}${specifier}`,
+ ts.ScriptTarget.ESNext,
+ ),
+ `failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
+ );
+}
+// this helps ensure as much as possible is in memory that is re-usable
+// before the snapshotting is done, which helps unsure fast "startup" for
+// subsequent uses of tsc in Deno.
+const TS_SNAPSHOT_PROGRAM = ts.createProgram({
+ rootNames: [buildSpecifier],
+ options: SNAPSHOT_COMPILE_OPTIONS,
+ host,
+});
+assert(
+ ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0,
+ "lib.d.ts files have errors",
+);
+
+// remove this now that we don't need it anymore for warming up tsc
+SOURCE_FILE_CACHE.delete(buildSpecifier);
+
+// exposes the functions that are called by `tsc::exec()` when type
+// checking TypeScript.
+globalThis.exec = exec;
+globalThis.getAssets = getAssets;
+
+// exposes the functions that are called when the compiler is used as a
+// language service.
+globalThis.serverMainLoop = serverMainLoop;