From 9ca60f45672b2582f646992570115ee727ea5022 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 16 Mar 2023 16:48:27 -0400 Subject: [PATCH 1/7] Reintegrate old branch --- package-lock.json | 4 +- src/compiler/_namespaces/ts.ts | 1 + src/compiler/builderState.ts | 2 +- src/compiler/checker.ts | 126 +++++++++++++--- src/compiler/commandLineParser.ts | 4 +- src/compiler/deno.ts | 233 ++++++++++++++++++++++++++++++ src/compiler/types.ts | 2 +- src/services/completions.ts | 3 +- 8 files changed, 349 insertions(+), 26 deletions(-) create mode 100644 src/compiler/deno.ts diff --git a/package-lock.json b/package-lock.json index 1950a8d9f661d..5729a69623267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typescript", - "version": "5.0.0", + "version": "5.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "typescript", - "version": "5.0.0", + "version": "5.0.2", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/src/compiler/_namespaces/ts.ts b/src/compiler/_namespaces/ts.ts index 5a01767f96d44..420aac81c4c25 100644 --- a/src/compiler/_namespaces/ts.ts +++ b/src/compiler/_namespaces/ts.ts @@ -29,6 +29,7 @@ export * from "../moduleNameResolver"; export * from "../binder"; export * from "../symbolWalker"; export * from "../checker"; +export * as deno from "../deno"; export * from "../visitorPublic"; export * from "../sourcemap"; export * from "../transformers/utilities"; diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index bcb0249061642..2efed02fadb75 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -258,7 +258,7 @@ export namespace BuilderState { } // From ambient modules - for (const ambientModule of program.getTypeChecker().getAmbientModules()) { + for (const ambientModule of program.getTypeChecker().getAmbientModules(sourceFile)) { if (ambientModule.declarations && ambientModule.declarations.length > 1) { addReferenceFromAmbientModule(ambientModule); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e374efbfba36..bdc5863a7e902 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -129,6 +129,7 @@ import { deduplicate, DefaultClause, defaultMaximumTruncationLength, + deno, DeferredTypeReference, DeleteExpression, Diagnostic, @@ -1457,6 +1458,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var nodeBuilder = createNodeBuilder(); var globals = createSymbolTable(); + var nodeGlobals = createSymbolTable(); var undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); undefinedSymbol.declarations = []; @@ -1465,6 +1467,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { globalThisSymbol.declarations = []; globals.set(globalThisSymbol.escapedName, globalThisSymbol); + const denoContext = deno.createDenoForkContext({ + globals, + nodeGlobals, + mergeSymbol, + ambientModuleSymbolRegex, + }); + + const nodeGlobalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + nodeGlobalThisSymbol.exports = denoContext.combinedGlobals; + nodeGlobalThisSymbol.declarations = []; + nodeGlobals.set(nodeGlobalThisSymbol.escapedName, nodeGlobalThisSymbol); + var argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); var requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); var isolatedModulesLikeFlagName = compilerOptions.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules"; @@ -2054,6 +2068,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var reverseMappedCache = new Map(); var inInferTypeForHomomorphicMappedType = false; var ambientModulesCache: Symbol[] | undefined; + var nodeAmbientModulesCache: Symbol[] | undefined; /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. @@ -2499,7 +2514,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Do not report an error when merging `var globalThis` with the built-in `globalThis`, // as we will already report a "Declaration name conflicts..." error, and this error // won't make much sense. - if (target !== globalThisSymbol) { + if (target !== globalThisSymbol && target !== nodeGlobalThisSymbol) { error( source.declarations && getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, @@ -2593,7 +2608,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (isGlobalScopeAugmentation(moduleAugmentation)) { - mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + denoContext.mergeGlobalSymbolTable(moduleAugmentation, moduleAugmentation.symbol.exports!) } else { // find a module that about to be augmented @@ -3337,7 +3352,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (!excludeGlobals) { - result = lookup(globals, name, meaning); + if (denoContext.hasNodeSourceFile(lastLocation)) { + result = lookup(nodeGlobals, name, meaning); + } + if (!result) { + result = lookup(globals, name, meaning); + } } } if (!result) { @@ -3936,7 +3956,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const usageMode = file && getUsageModeForExpression(usage); if (file && usageMode !== undefined) { const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); - if (usageMode === ModuleKind.ESNext || result) { + // deno: removed condition in typescript here (https://github.com/microsoft/TypeScript/issues/51321) + if (result) { return result; } // fallthrough on cjs usages so we imply defaults for interop'd imports, too @@ -4826,6 +4847,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { + const result = resolveExternalModuleInner(location, moduleReference, moduleNotFoundError, errorNode, isForAugmentation); + + // deno: attempt to resolve an npm package reference to its bare specifier w/ path ambient module + // when not found and the symbol has zero exports + if (moduleReference.startsWith("npm:") && (result == null || result?.exports?.size === 0)) { + const npmPackageRef = deno.tryParseNpmPackageReference(moduleReference); + if (npmPackageRef) { + const bareSpecifier = npmPackageRef.name + (npmPackageRef.subPath == null ? "" : "/" + npmPackageRef.subPath); + const ambientModule = tryFindAmbientModule(bareSpecifier, /*withAugmentations*/ true); + if (ambientModule) { + return ambientModule; + } + } + } + + return result; + } + + function resolveExternalModuleInner(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { if (startsWith(moduleReference, "@types/")) { const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); @@ -5720,6 +5760,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + if (denoContext.hasNodeSourceFile(enclosingDeclaration)) { + result = callback(nodeGlobals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + if (result) { + return result; + } + } + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); } @@ -5814,7 +5861,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that - return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); + if (result) { + return result; + } + const globalSymbol = symbols === nodeGlobals ? nodeGlobalThisSymbol : symbols === globals ? globalThisSymbol : undefined; + return globalSymbol != null ? getCandidateListForSymbol(globalSymbol, globalSymbol, ignoreQualification) : undefined; } function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { @@ -13012,7 +13063,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let indexInfos: IndexInfo[] | undefined; if (symbol.exports) { members = getExportsOfSymbol(symbol); - if (symbol === globalThisSymbol) { + if (symbol === globalThisSymbol || symbol === nodeGlobalThisSymbol) { const varsOnly = new Map() as SymbolTable; members.forEach(p => { if (!(p.flags & SymbolFlags.BlockScoped) && !(p.flags & SymbolFlags.ValueModule && p.declarations?.length && every(p.declarations, isAmbientModule))) { @@ -14293,7 +14344,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isExternalModuleNameRelative(moduleName)) { return undefined; } - const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); + const symbol = getSymbol(denoContext.combinedGlobals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); // merged symbol is module declaration symbol combined with all augmentations return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; } @@ -17283,6 +17334,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } + // deno: ensure condition and body match the above + else if (objectType.symbol === nodeGlobalThisSymbol && propName !== undefined && nodeGlobalThisSymbol.exports!.has(propName) && (nodeGlobalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { + error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + } else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { const typeName = typeToString(objectType); @@ -28092,7 +28147,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); if (noImplicitThis) { const globalThisType = getTypeOfSymbol(globalThisSymbol); - if (type === globalThisType && capturedByArrowFunction) { + if ((type === globalThisType || type === getTypeOfSymbol(nodeGlobalThisSymbol)) && capturedByArrowFunction) { error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); } else if (!type) { @@ -28152,6 +28207,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefinedType; } else if (includeGlobalThis) { + if (denoContext.hasNodeSourceFile(container)) { + return getTypeOfSymbol(nodeGlobalThisSymbol); + } return getTypeOfSymbol(globalThisSymbol); } } @@ -31256,6 +31314,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return anyType; } + // deno: ensure condition matches above + if (leftType.symbol === nodeGlobalThisSymbol) { + // deno: don't bother with errors like above for simplicity + return anyType; + } if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS); } @@ -31578,7 +31641,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. if (symbol) return symbol; let candidates: Symbol[]; - if (symbols === globals) { + if (symbols === globals || symbols === nodeGlobals) { const primitives = mapDefined( ["string", "number", "boolean", "object", "bigint", "symbol"], s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String) @@ -44097,7 +44160,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol === nodeGlobalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } else { @@ -44891,6 +44954,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { location = location.parent; } + if (denoContext.hasNodeSourceFile(location)) { + copySymbols(nodeGlobals, meaning); + } + copySymbols(globals, meaning); } @@ -46078,6 +46145,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function hasGlobalName(name: string): boolean { + // deno: seems ok not to bother with nodeGlobals here since + // this is just a public api function that we don't bother with + // NOTICE: Make sure to check that's still the case when upgrading!! return globals.has(escapeLeadingUnderscores(name)); } @@ -46451,10 +46521,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } } - mergeSymbolTable(globals, file.locals!); + denoContext.mergeGlobalSymbolTable(file, file.locals!); } if (file.jsGlobalAugmentations) { - mergeSymbolTable(globals, file.jsGlobalAugmentations); + denoContext.mergeGlobalSymbolTable(file, file.jsGlobalAugmentations); } if (file.patternAmbientModules && file.patternAmbientModules.length) { patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); @@ -46465,9 +46535,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (file.symbol && file.symbol.globalExports) { // Merge in UMD exports with first-in-wins semantics (see #9771) const source = file.symbol.globalExports; + const isNodeFile = denoContext.hasNodeSourceFile(file); source.forEach((sourceSymbol, id) => { - if (!globals.has(id)) { - globals.set(id, sourceSymbol); + const envGlobals = isNodeFile ? denoContext.getGlobalsForName(id) : globals; + if (!envGlobals.has(id)) { + envGlobals.set(id, sourceSymbol); } }); } @@ -46497,6 +46569,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); getSymbolLinks(unknownSymbol).type = errorType; getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); + getSymbolLinks(nodeGlobalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, nodeGlobalThisSymbol); // Initialize special types globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); @@ -48348,17 +48421,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } - function getAmbientModules(): Symbol[] { - if (!ambientModulesCache) { - ambientModulesCache = []; - globals.forEach((global, sym) => { + function getAmbientModules(sourceFile?: SourceFile): Symbol[] { + const isNode = denoContext.hasNodeSourceFile(sourceFile); + if (isNode) { + if (!nodeAmbientModulesCache) { + nodeAmbientModulesCache = getAmbientModules(denoContext.combinedGlobals); + } + return nodeAmbientModulesCache; + } else { + if (!ambientModulesCache) { + ambientModulesCache = getAmbientModules(globals); + } + return ambientModulesCache; + } + + function getAmbientModules(envGlobals: SymbolTable) { + const cache: Symbol[] = []; + envGlobals.forEach((global, sym) => { // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. if (ambientModuleSymbolRegex.test(sym as string)) { - ambientModulesCache!.push(global); + cache!.push(global); } }); + return cache; } - return ambientModulesCache; } function checkGrammarImportClause(node: ImportClause): boolean { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 629063de29808..33158641671d6 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -161,6 +161,8 @@ const libEntries: [string, string][] = [ // Host only ["dom", "lib.dom.d.ts"], ["dom.iterable", "lib.dom.iterable.d.ts"], + ["dom.asynciterable", "lib.dom.asynciterable.d.ts"], + ["dom.extras", "lib.dom.extras.d.ts"], ["webworker", "lib.webworker.d.ts"], ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], ["webworker.iterable", "lib.webworker.iterable.d.ts"], @@ -211,7 +213,7 @@ const libEntries: [string, string][] = [ ["es2022.string", "lib.es2022.string.d.ts"], ["es2022.regexp", "lib.es2022.regexp.d.ts"], ["es2023.array", "lib.es2023.array.d.ts"], - ["esnext.array", "lib.es2023.array.d.ts"], + ["esnext.array", "lib.esnext.array.d.ts"], ["esnext.symbol", "lib.es2019.symbol.d.ts"], ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], ["esnext.intl", "lib.esnext.intl.d.ts"], diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts new file mode 100644 index 0000000000000..8c60adb871905 --- /dev/null +++ b/src/compiler/deno.ts @@ -0,0 +1,233 @@ +import * as ts from "./_namespaces/ts"; + +export type IsNodeSourceFileCallback = (sourceFile: ts.SourceFile) => boolean; + +let isNodeSourceFile: IsNodeSourceFileCallback = () => false; +let nodeBuiltInModuleNames = new Set(); + +export function setIsNodeSourceFileCallback(callback: IsNodeSourceFileCallback) { + isNodeSourceFile = callback; +} + +export function setNodeBuiltInModuleNames(names: string[]) { + nodeBuiltInModuleNames = new Set(names); +} + +// When upgrading: +// 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts +// - Beware that `globalThisType` might refer to the global `this` type +// and not the global `globalThis` type +// 2. Inspect the types in @types/node for anything that might need to go below +// as well. + +const nodeOnlyGlobalNames = new Set([ + "NodeRequire", + "RequireResolve", + "RequireResolve", + "process", + "console", + "__filename", + "__dirname", + "require", + "module", + "exports", + "gc", + "BufferEncoding", + "BufferConstructor", + "WithImplicitCoercion", + "Buffer", + "Console", + "ImportMeta", + "setTimeout", + "setInterval", + "setImmediate", + "Global", + "AbortController", + "AbortSignal", + "Blob", + "BroadcastChannel", + "MessageChannel", + "MessagePort", + "Event", + "EventTarget", + "performance", + "TextDecoder", + "TextEncoder", + "URL", + "URLSearchParams", +]) as Set; + +export function createDenoForkContext({ + mergeSymbol, + globals, + nodeGlobals, + ambientModuleSymbolRegex, +}: { + mergeSymbol(target: ts.Symbol, source: ts.Symbol, unidirectional?: boolean): ts.Symbol; + globals: ts.SymbolTable; + nodeGlobals: ts.SymbolTable; + ambientModuleSymbolRegex: RegExp, +}) { + return { + hasNodeSourceFile, + getGlobalsForName, + mergeGlobalSymbolTable, + combinedGlobals: createNodeGlobalsSymbolTable(), + }; + + function hasNodeSourceFile(node: ts.Node | undefined) { + if (!node) return false; + const sourceFile = ts.getSourceFileOfNode(node); + return isNodeSourceFile(sourceFile); + } + + function getGlobalsForName(id: ts.__String) { + // Node ambient modules are only accessible in the node code, + // so put them on the node globals + if (ambientModuleSymbolRegex.test(id as string)) { + if ((id as string).startsWith('"node:')) { + // check if it's a node specifier that we support + const name = (id as string).slice(6, -1); + if (nodeBuiltInModuleNames.has(name)) { + return globals; + } + } + return nodeGlobals; + } + return nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals; + } + + function mergeGlobalSymbolTable(node: ts.Node, source: ts.SymbolTable, unidirectional = false) { + const sourceFile = ts.getSourceFileOfNode(node); + const isNodeFile = hasNodeSourceFile(sourceFile); + source.forEach((sourceSymbol, id) => { + const target = isNodeFile ? getGlobalsForName(id) : globals; + const targetSymbol = target.get(id); + target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); + }); + } + + function createNodeGlobalsSymbolTable() { + return new Proxy(globals, { + get(target, prop: string | symbol, receiver) { + if (prop === "get") { + return (key: ts.__String) => { + return nodeGlobals.get(key) ?? globals.get(key); + }; + } else if (prop === "has") { + return (key: ts.__String) => { + return nodeGlobals.has(key) || globals.has(key); + }; + } else if (prop === "size") { + let i = 0; + forEachEntry(() => { + i++; + }); + return i; + } else if (prop === "forEach") { + return (action: (value: ts.Symbol, key: ts.__String) => void) => { + forEachEntry(([key, value]) => { + action(value, key); + }); + }; + } else if (prop === "entries") { + return () => { + return getEntries(kv => kv); + }; + } else if (prop === "keys") { + return () => { + return getEntries(kv => kv[0]); + }; + } else if (prop === "values") { + return () => { + return getEntries(kv => kv[1]); + }; + } else if (prop === Symbol.iterator) { + return () => { + // Need to convert this to an array since typescript targets ES5 + // and providing back the iterator won't work here. I don't want + // to change the target to ES6 because I'm not sure if that would + // surface any issues. + return ts.arrayFrom(getEntries(kv => kv))[Symbol.iterator](); + }; + } else { + const value = (target as any)[prop]; + if (value instanceof Function) { + return function (this: any, ...args: any[]) { + return value.apply(this === receiver ? target : this, args); + }; + } + return value; + } + }, + }); + + function forEachEntry(action: (value: [ts.__String, ts.Symbol]) => void) { + const iterator = getEntries((entry) => { + action(entry); + }); + // drain the iterator to do the action + while (!iterator.next().done) {} + } + + // todo(THIS PR): Can move away from this now :) + function* getEntries( + transform: (value: [ts.__String, ts.Symbol]) => R + ) { + const foundKeys = new Set(); + for (const entries of [nodeGlobals.entries(), globals.entries()]) { + let next = entries.next(); + while (!next.done) { + if (!foundKeys.has(next.value[0])) { + yield transform(next.value); + foundKeys.add(next.value[0]); + } + next = entries.next(); + } + } + } + } +} + +export interface NpmPackageReference { + name: string; + versionReq: string; + subPath: string | undefined; +} + +export function tryParseNpmPackageReference(text: string) { + try { + return parseNpmPackageReference(text); + } catch { + return undefined; + } +} + +export function parseNpmPackageReference(text: string) { + if (!text.startsWith("npm:")) { + throw new Error(`Not an npm specifier: ${text}`); + } + text = text.replace(/^npm:\/?/, ""); // todo: remove this regex + const parts = text.split("/"); + const namePartLen = text.startsWith("@") ? 2 : 1; + if (parts.length < namePartLen) { + throw new Error(`Not a valid package: ${text}`); + } + const nameParts = parts.slice(0, namePartLen); + const lastNamePart = nameParts.at(-1)!; + const lastAtIndex = lastNamePart.lastIndexOf("@"); + let versionReq: string | undefined = undefined; + if (lastAtIndex > 0) { + versionReq = lastNamePart.substring(lastAtIndex + 1); + nameParts[nameParts.length - 1] = lastNamePart.substring(0, lastAtIndex); + } + const name = nameParts.join("/"); + if (name.length === 0) { + throw new Error(`Npm specifier did not have a name: ${text}`); + } + return { + name, + versionReq, + subPath: parts.length > nameParts.length ? parts.slice(nameParts.length).join("/") : undefined, + }; +} diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 881f3576868cd..83b78876e4222 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5078,7 +5078,7 @@ export interface TypeChecker { /** @internal */ forEachExportAndPropertyOfModule(moduleSymbol: Symbol, cb: (symbol: Symbol, key: __String) => void): void; getJsxIntrinsicTagNamesAt(location: Node): Symbol[]; isOptionalParameter(node: ParameterDeclaration): boolean; - getAmbientModules(): Symbol[]; + getAmbientModules(sourceFile?: SourceFile): Symbol[]; tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined; /** diff --git a/src/services/completions.ts b/src/services/completions.ts index 5005639f8898f..7697b86dce26a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -4949,7 +4949,8 @@ function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeC if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { return true; } - const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + // deno: provide sourceFile so that it can figure out if it's a node or deno globalThis + const globalThisSymbol = checker.resolveName("globalThis", /*location*/ sourceFile, SymbolFlags.Value, /*excludeGlobals*/ false); if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { return true; } From 4182ba6f5db198ae2fae4cb252a45c1f9f5bb728 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 16 Mar 2023 17:25:22 -0400 Subject: [PATCH 2/7] Temporarily disable this diagnostic --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bdc5863a7e902..ba993fee03823 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44268,7 +44268,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (node.isExportEquals) { // Forbid export= in esm implementation files, and esm mode declaration files if (moduleKind >= ModuleKind.ES2015 && - ((node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) || + // deno: temporarily disable this one until Deno 2.0 (https://github.com/microsoft/TypeScript/pull/52109) + (/* (node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) || */ (!(node.flags & NodeFlags.Ambient) && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS))) { // export assignment is not supported in es6 modules grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); From cfeae926ad00f25fb69ef107778527d08213eda3 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 17 Mar 2023 17:22:28 -0400 Subject: [PATCH 3/7] Move node global names into the cli repo --- src/compiler/deno.ts | 83 +++++++++++--------------------------------- 1 file changed, 20 insertions(+), 63 deletions(-) diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index 8c60adb871905..cbacf476dd824 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -4,58 +4,25 @@ export type IsNodeSourceFileCallback = (sourceFile: ts.SourceFile) => boolean; let isNodeSourceFile: IsNodeSourceFileCallback = () => false; let nodeBuiltInModuleNames = new Set(); +let nodeOnlyGlobalNames = new Set(); export function setIsNodeSourceFileCallback(callback: IsNodeSourceFileCallback) { isNodeSourceFile = callback; } -export function setNodeBuiltInModuleNames(names: string[]) { +export function setNodeBuiltInModuleNames(names: readonly string[]) { nodeBuiltInModuleNames = new Set(names); } -// When upgrading: -// 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts -// - Beware that `globalThisType` might refer to the global `this` type -// and not the global `globalThis` type -// 2. Inspect the types in @types/node for anything that might need to go below -// as well. +export function setNodeOnlyGlobalNames(names: readonly string[]) { + nodeBuiltInModuleNames = new Set(names); + nodeOnlyGlobalNames = new Set(names) as Set; +} -const nodeOnlyGlobalNames = new Set([ - "NodeRequire", - "RequireResolve", - "RequireResolve", - "process", - "console", - "__filename", - "__dirname", - "require", - "module", - "exports", - "gc", - "BufferEncoding", - "BufferConstructor", - "WithImplicitCoercion", - "Buffer", - "Console", - "ImportMeta", - "setTimeout", - "setInterval", - "setImmediate", - "Global", - "AbortController", - "AbortSignal", - "Blob", - "BroadcastChannel", - "MessageChannel", - "MessagePort", - "Event", - "EventTarget", - "performance", - "TextDecoder", - "TextEncoder", - "URL", - "URLSearchParams", -]) as Set; +// When upgrading: +// Inspect all usages of "globals" and "globalThisSymbol" in checker.ts +// - Beware that `globalThisType` might refer to the global `this` type +// and not the global `globalThis` type export function createDenoForkContext({ mergeSymbol, @@ -120,15 +87,15 @@ export function createDenoForkContext({ }; } else if (prop === "size") { let i = 0; - forEachEntry(() => { + for (const _ignore of getEntries(entry => entry)) { i++; - }); + } return i; } else if (prop === "forEach") { return (action: (value: ts.Symbol, key: ts.__String) => void) => { - forEachEntry(([key, value]) => { + for (const [key, value] of getEntries(entry => entry)) { action(value, key); - }); + } }; } else if (prop === "entries") { return () => { @@ -148,7 +115,7 @@ export function createDenoForkContext({ // and providing back the iterator won't work here. I don't want // to change the target to ES6 because I'm not sure if that would // surface any issues. - return ts.arrayFrom(getEntries(kv => kv))[Symbol.iterator](); + return Array.from(getEntries(kv => kv))[Symbol.iterator](); }; } else { const value = (target as any)[prop]; @@ -162,27 +129,17 @@ export function createDenoForkContext({ }, }); - function forEachEntry(action: (value: [ts.__String, ts.Symbol]) => void) { - const iterator = getEntries((entry) => { - action(entry); - }); - // drain the iterator to do the action - while (!iterator.next().done) {} - } - - // todo(THIS PR): Can move away from this now :) function* getEntries( transform: (value: [ts.__String, ts.Symbol]) => R ) { const foundKeys = new Set(); + // prefer the node globals over the deno globalThis for (const entries of [nodeGlobals.entries(), globals.entries()]) { - let next = entries.next(); - while (!next.done) { - if (!foundKeys.has(next.value[0])) { - yield transform(next.value); - foundKeys.add(next.value[0]); + for (const entry of entries) { + if (!foundKeys.has(entry[0])) { + yield transform(entry); + foundKeys.add(entry[0]); } - next = entries.next(); } } } From f259cc61639bedd0060d2377b00fc1fd86f77272 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 21 Mar 2023 16:40:51 -0400 Subject: [PATCH 4/7] perf: ensure compiler options affecting semantic diagnostics get included in build info --- src/compiler/builder.ts | 3 ++- src/compiler/commandLineParser.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index b1597969ad776..0c6cdfdf7d3d7 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1183,7 +1183,8 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde const { optionsNameMap } = getOptionsNameMap(); for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) { const optionInfo = optionsNameMap.get(name.toLowerCase()); - if (optionInfo?.affectsBuildInfo) { + // all semantic diagnostics affect build info + if (optionInfo && (optionInfo.affectsBuildInfo || optionInfo.affectsSemanticDiagnostics)) { (result ||= {})[name] = convertToReusableCompilerOptionValue( optionInfo, options[name] as CompilerOptionsValue, diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 33158641671d6..9c9788a313f27 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1103,6 +1103,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [ name: "allowImportingTsExtensions", type: "boolean", affectsSemanticDiagnostics: true, + affectsBuildInfo: true, category: Diagnostics.Modules, description: Diagnostics.Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noEmit_or_emitDeclarationOnly_to_be_set, defaultValueDescription: false, From 972d6117e6fe470ca4ff261d6a3923b2267e17cb Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 22 Mar 2023 17:42:34 -0400 Subject: [PATCH 5/7] Revert. --- src/compiler/builder.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 0c6cdfdf7d3d7..b1597969ad776 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1183,8 +1183,7 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde const { optionsNameMap } = getOptionsNameMap(); for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) { const optionInfo = optionsNameMap.get(name.toLowerCase()); - // all semantic diagnostics affect build info - if (optionInfo && (optionInfo.affectsBuildInfo || optionInfo.affectsSemanticDiagnostics)) { + if (optionInfo?.affectsBuildInfo) { (result ||= {})[name] = convertToReusableCompilerOptionValue( optionInfo, options[name] as CompilerOptionsValue, From e9235185d807d672ade37f9b78ff44dc4b576eac Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 31 Mar 2023 11:09:50 -0400 Subject: [PATCH 6/7] Fix data urls --- src/compiler/path.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/path.ts b/src/compiler/path.ts index b2e7d890185a1..e4d850c612980 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -214,6 +214,11 @@ function getEncodedRootLength(path: string): number { return ~path.length; // URL: "file://server", "http://server" } + // deno: temporary hack until https://github.com/microsoft/TypeScript/issues/53605 is fixed + if (path.startsWith("data:")) { + return ~path.length; + } + // relative return 0; } From 7c0c7d3700d1b77c4a57e68a60848aa673ab70ff Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 31 Mar 2023 11:45:22 -0400 Subject: [PATCH 7/7] Don't add trailing slashes to data urls --- src/compiler/path.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/path.ts b/src/compiler/path.ts index e4d850c612980..e58583dd6270b 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -703,6 +703,11 @@ export function ensureTrailingDirectorySeparator(path: string): string; /** @internal */ export function ensureTrailingDirectorySeparator(path: string) { if (!hasTrailingDirectorySeparator(path)) { + // deno: added this so that data urls don't get a trailing slash + // https://github.com/microsoft/TypeScript/issues/53605#issuecomment-1492167313 + if (path.startsWith("data:")) { + return path; + } return path + directorySeparator; }