From ed3da83d49bd5ce97c8bf5190fa95b91600b5990 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 4 Oct 2022 13:30:02 -0400 Subject: [PATCH 01/15] feat: separate node and deno global contexts --- src/compiler/checker.ts | 71 +++++++++++++--- src/compiler/deno.ts | 161 ++++++++++++++++++++++++++++++++++++ src/compiler/tsconfig.json | 1 + src/services/completions.ts | 3 +- src/tsconfig-base.json | 2 +- 5 files changed, 225 insertions(+), 13 deletions(-) create mode 100644 src/compiler/deno.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 194f51256860b..c958b6c828783 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -375,6 +375,7 @@ namespace ts { const nodeBuilder = createNodeBuilder(); const globals = createSymbolTable(); + const nodeGlobals = createSymbolTable(); const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); undefinedSymbol.declarations = []; @@ -383,6 +384,17 @@ namespace ts { globalThisSymbol.declarations = []; globals.set(globalThisSymbol.escapedName, globalThisSymbol); + const denoContext = ts.deno.createDenoContext({ + globals, + nodeGlobals, + mergeSymbol, + }); + + const nodeGlobalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + nodeGlobalThisSymbol.exports = denoContext.combinedGlobals; + nodeGlobalThisSymbol.declarations = []; + nodeGlobals.set(nodeGlobalThisSymbol.escapedName, nodeGlobalThisSymbol); + const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); @@ -1347,7 +1359,7 @@ namespace ts { // 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, @@ -1441,7 +1453,7 @@ namespace ts { } if (isGlobalScopeAugmentation(moduleAugmentation)) { - mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + denoContext.mergeGlobalSymbolTable(moduleAugmentation, moduleAugmentation.symbol.exports!); } else { // find a module that about to be augmented @@ -2176,7 +2188,12 @@ namespace ts { } 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) { @@ -4404,6 +4421,13 @@ namespace ts { } } + if (denoContext.hasNodeSourceFile(enclosingDeclaration)) { + result = callback(nodeGlobals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + if (result) { + return result; + } + } + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); } @@ -4498,7 +4522,11 @@ namespace ts { }); // 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) { @@ -11634,7 +11662,7 @@ namespace ts { 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))) { @@ -15740,6 +15768,10 @@ namespace ts { 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); @@ -25119,7 +25151,7 @@ namespace ts { if (type.flags & TypeFlags.Union || type.flags & TypeFlags.Object && declaredType !== type && !(declaredType === unknownType && isEmptyAnonymousObjectType(type)) || isThisTypeParameter(type) - || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) { + || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol && t.symbol !== nodeGlobalThisSymbol)) { return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); } return type; @@ -26379,6 +26411,9 @@ namespace ts { return undefinedType; } else if (includeGlobalThis) { + if (denoContext.hasNodeSourceFile(container)) { + return getTypeOfSymbol(nodeGlobalThisSymbol); + } return getTypeOfSymbol(globalThisSymbol); } } @@ -29356,6 +29391,11 @@ namespace ts { } 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); } @@ -41549,7 +41589,7 @@ namespace ts { // 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 { @@ -42297,6 +42337,10 @@ namespace ts { location = location.parent; } + if (denoContext.hasNodeSourceFile(location)) { + copySymbols(nodeGlobals, meaning); + } + copySymbols(globals, meaning); } @@ -43815,10 +43859,10 @@ namespace ts { 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); @@ -43829,9 +43873,11 @@ namespace ts { 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); } }); } @@ -43861,6 +43907,7 @@ namespace ts { 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); @@ -45616,6 +45663,8 @@ namespace ts { function getAmbientModules(): Symbol[] { if (!ambientModulesCache) { ambientModulesCache = []; + // deno: don't bother including ambient modules from nodeGlobals here + // because we don't want them showing up in completions globals.forEach((global, sym) => { // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. if (ambientModuleSymbolRegex.test(sym as string)) { diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts new file mode 100644 index 0000000000000..6bcb4e1efe813 --- /dev/null +++ b/src/compiler/deno.ts @@ -0,0 +1,161 @@ +/* @internal */ +namespace ts.deno { + export type IsNodeSourceFileCallback = (sourceFile: SourceFile) => boolean; + + let isNodeSourceFile: IsNodeSourceFileCallback = () => false; + + export function setIsNodeSourceFileCallback(callback: IsNodeSourceFileCallback) { + isNodeSourceFile = callback; + } + + // 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 "special" typescript types and add them to the list below. + // 3. Inspect the types in @types/node for anything that might need to go below + // as well. + + const ignoredGlobalNames = new Set([ + // checker.ts "special" types + "Object", + "Function", + "CallableFunction", + "NewableFunction", + "Array", + "ReadonlyArray", + "String", + "Number", + "Boolean", + "RegExpr", + "ThisType", + "NonNullable", + // types in @types/node that we don't want to create duplicates of + "Int8Array", + "Uint8ClampedArray", + "Int16Array", + "Uint16Array", + "Int32Array", + "Float32Array", + "Float64Array", + "BigInt64Array", + "BigUint64Array", + ].map(s => s as ts.__String)); + + export function createDenoContext({ + mergeSymbol, + globals, + nodeGlobals, + }: { + mergeSymbol(target: Symbol, source: Symbol, unidirectional?: boolean): Symbol; + globals: SymbolTable; + nodeGlobals: SymbolTable; + }) { + return { + hasNodeSourceFile, + isAllowedNodeGlobalName, + getGlobalsForName, + mergeGlobalSymbolTable, + combinedGlobals: createNodeGlobalsSymbolTable(), + }; + + function hasNodeSourceFile(node: Node | undefined) { + if (!node) return false; + const sourceFile = getSourceFileOfNode(node); + return isNodeSourceFile(sourceFile); + } + + function isAllowedNodeGlobalName(id: ts.__String) { + return !ignoredGlobalNames.has(id); + } + + function getGlobalsForName(id: ts.__String) { + return isAllowedNodeGlobalName(id) ? nodeGlobals : globals; + } + + function mergeGlobalSymbolTable(node: Node, source: SymbolTable, unidirectional = false) { + const isNodeFile = hasNodeSourceFile(node); + 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: 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 () => { + return getEntries(kv => kv); + }; + } 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: [__String, Symbol]) => void) { + const iterator = getEntries((entry) => { + action(entry); + }); + // drain the iterator to do the action + while (!iterator.next().done) {} + } + + function* getEntries( + transform: (value: [__String, 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(); + } + } + } + } + } +} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index fccbe06558a23..ab8986c9df11e 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -42,6 +42,7 @@ "binder.ts", "symbolWalker.ts", "checker.ts", + "deno.ts", "visitorPublic.ts", "sourcemap.ts", "transformers/utilities.ts", diff --git a/src/services/completions.ts b/src/services/completions.ts index c24bc8dcaf165..5cdbceb88230b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -4209,7 +4209,8 @@ namespace ts.Completions { 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; } diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index 51cf414728dc0..fa28c12613773 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -2,7 +2,7 @@ "compilerOptions": { "pretty": true, "lib": ["es2015.iterable", "es2015.generator", "es5"], - "target": "es5", + "target": "es6", "moduleResolution": "node", "rootDir": ".", From a22ce84e3900e3d1fa51d24138933cd7502a7ad6 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 4 Oct 2022 18:17:40 -0400 Subject: [PATCH 02/15] Expose non-@types/node globals to deno code --- src/compiler/checker.ts | 2 +- src/compiler/deno.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c958b6c828783..97946158bc202 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43875,7 +43875,7 @@ namespace ts { const source = file.symbol.globalExports; const isNodeFile = denoContext.hasNodeSourceFile(file); source.forEach((sourceSymbol, id) => { - const envGlobals = isNodeFile ? denoContext.getGlobalsForName(id) : globals; + const envGlobals = isNodeFile ? denoContext.getGlobalsForName(file, id) : globals; if (!envGlobals.has(id)) { envGlobals.set(id, sourceSymbol); } diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index 6bcb4e1efe813..bdf225a75ae62 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -69,14 +69,18 @@ namespace ts.deno { return !ignoredGlobalNames.has(id); } - function getGlobalsForName(id: ts.__String) { - return isAllowedNodeGlobalName(id) ? nodeGlobals : globals; + function getGlobalsForName(sourceFile: SourceFile, id: ts.__String) { + if (!isAllowedNodeGlobalName(id)) + return globals; + const isTypesNodeFile = sourceFile.fileName.includes("/@types/node/"); + return isTypesNodeFile || nodeGlobals.has(id) ? nodeGlobals : globals; } function mergeGlobalSymbolTable(node: Node, source: SymbolTable, unidirectional = false) { - const isNodeFile = hasNodeSourceFile(node); + const sourceFile = getSourceFileOfNode(node); + const isNodeFile = hasNodeSourceFile(sourceFile); source.forEach((sourceSymbol, id) => { - const target = isNodeFile ? getGlobalsForName(id) : globals; + const target = isNodeFile ? getGlobalsForName(sourceFile, id) : globals; const targetSymbol = target.get(id); target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); }); From d41713291114c9eeca24d44f60780fc63d3d759f Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 5 Oct 2022 09:55:21 -0400 Subject: [PATCH 03/15] Opt-in instead of opt-out --- src/compiler/checker.ts | 5 +-- src/compiler/deno.ts | 77 ++++++++++++++++++++--------------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 97946158bc202..5fb8a24884062 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -384,10 +384,11 @@ namespace ts { globalThisSymbol.declarations = []; globals.set(globalThisSymbol.escapedName, globalThisSymbol); - const denoContext = ts.deno.createDenoContext({ + const denoContext = ts.deno.createDenoForkContext({ globals, nodeGlobals, mergeSymbol, + ambientModuleSymbolRegex, }); const nodeGlobalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); @@ -43875,7 +43876,7 @@ namespace ts { const source = file.symbol.globalExports; const isNodeFile = denoContext.hasNodeSourceFile(file); source.forEach((sourceSymbol, id) => { - const envGlobals = isNodeFile ? denoContext.getGlobalsForName(file, id) : globals; + const envGlobals = isNodeFile ? denoContext.getGlobalsForName(id) : globals; if (!envGlobals.has(id)) { envGlobals.set(id, sourceSymbol); } diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index bdf225a75ae62..03354394c90f9 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -12,48 +12,50 @@ namespace ts.deno { // 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 "special" typescript types and add them to the list below. - // 3. Inspect the types in @types/node for anything that might need to go below + // 2. Inspect the types in @types/node for anything that might need to go below // as well. - const ignoredGlobalNames = new Set([ - // checker.ts "special" types - "Object", - "Function", - "CallableFunction", - "NewableFunction", - "Array", - "ReadonlyArray", - "String", - "Number", - "Boolean", - "RegExpr", - "ThisType", - "NonNullable", - // types in @types/node that we don't want to create duplicates of - "Int8Array", - "Uint8ClampedArray", - "Int16Array", - "Uint16Array", - "Int32Array", - "Float32Array", - "Float64Array", - "BigInt64Array", - "BigUint64Array", - ].map(s => s as ts.__String)); + 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", + // todo: these should be defined similar to `URL` in `@types/node` + // so they don't conflict with the global deno declaration + "AbortController", + "AbortSignal", + ]) as Set; - export function createDenoContext({ + export function createDenoForkContext({ mergeSymbol, globals, nodeGlobals, + ambientModuleSymbolRegex, }: { mergeSymbol(target: Symbol, source: Symbol, unidirectional?: boolean): Symbol; globals: SymbolTable; nodeGlobals: SymbolTable; + ambientModuleSymbolRegex: RegExp, }) { return { hasNodeSourceFile, - isAllowedNodeGlobalName, getGlobalsForName, mergeGlobalSymbolTable, combinedGlobals: createNodeGlobalsSymbolTable(), @@ -65,22 +67,19 @@ namespace ts.deno { return isNodeSourceFile(sourceFile); } - function isAllowedNodeGlobalName(id: ts.__String) { - return !ignoredGlobalNames.has(id); - } - - function getGlobalsForName(sourceFile: SourceFile, id: ts.__String) { - if (!isAllowedNodeGlobalName(id)) - return globals; - const isTypesNodeFile = sourceFile.fileName.includes("/@types/node/"); - return isTypesNodeFile || nodeGlobals.has(id) ? nodeGlobals : globals; + 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)) + return nodeGlobals; + return nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals; } function mergeGlobalSymbolTable(node: Node, source: SymbolTable, unidirectional = false) { const sourceFile = getSourceFileOfNode(node); const isNodeFile = hasNodeSourceFile(sourceFile); source.forEach((sourceSymbol, id) => { - const target = isNodeFile ? getGlobalsForName(sourceFile, id) : globals; + const target = isNodeFile ? getGlobalsForName(id) : globals; const targetSymbol = target.get(id); target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); }); From 8beebda5e4b205d7a7ab037d542ee2eb2137b251 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 5 Oct 2022 14:18:03 -0400 Subject: [PATCH 04/15] Back to ES5 --- src/compiler/deno.ts | 6 +++++- src/tsconfig-base.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index 03354394c90f9..d38ed7ff987a4 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -122,7 +122,11 @@ namespace ts.deno { }; } else if (prop === Symbol.iterator) { return () => { - return getEntries(kv => kv); + // 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 Array.from(getEntries(kv => kv)); }; } else { const value = (target as any)[prop]; diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index fa28c12613773..51cf414728dc0 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -2,7 +2,7 @@ "compilerOptions": { "pretty": true, "lib": ["es2015.iterable", "es2015.generator", "es5"], - "target": "es6", + "target": "es5", "moduleResolution": "node", "rootDir": ".", From a90a3d020fa70bf99bcef1f7f64b70225fcff865 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 5 Oct 2022 17:39:19 -0400 Subject: [PATCH 05/15] Fix iterator TypeError in proxy. --- src/compiler/deno.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index d38ed7ff987a4..ed387d035e0e6 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -126,7 +126,7 @@ namespace ts.deno { // 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 Array.from(getEntries(kv => kv)); + return arrayFrom(getEntries(kv => kv))[Symbol.iterator](); }; } else { const value = (target as any)[prop]; From d6dfd054cc3cbdab12840bb332da98d379804135 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 5 Oct 2022 19:24:12 -0400 Subject: [PATCH 06/15] Get ambient modules based on source file. --- src/compiler/builderState.ts | 2 +- src/compiler/checker.ts | 7 +++---- src/compiler/deno.ts | 4 ---- src/compiler/types.ts | 2 +- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index de300efd3cf05..a9915759e4c08 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -225,7 +225,7 @@ namespace ts { } // 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 5fb8a24884062..f825fd5f617d0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45661,12 +45661,11 @@ namespace ts { return false; } - function getAmbientModules(): Symbol[] { + function getAmbientModules(sourceFile?: SourceFile): Symbol[] { if (!ambientModulesCache) { ambientModulesCache = []; - // deno: don't bother including ambient modules from nodeGlobals here - // because we don't want them showing up in completions - globals.forEach((global, sym) => { + const envGlobals = denoContext.hasNodeSourceFile(sourceFile) ? denoContext.combinedGlobals : globals; + 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); diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index ed387d035e0e6..66e2625601327 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -37,10 +37,6 @@ namespace ts.deno { "setInterval", "setImmediate", "Global", - // todo: these should be defined similar to `URL` in `@types/node` - // so they don't conflict with the global deno declaration - "AbortController", - "AbortSignal", ]) as Set; export function createDenoForkContext({ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e7aa743008d69..000851c2b9e03 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4494,7 +4494,7 @@ namespace ts { /* @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; /** From 82a6d537b145c4d2b0c318e07753d08b2039b2a2 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 5 Oct 2022 20:44:04 -0400 Subject: [PATCH 07/15] Fix cache. --- src/compiler/checker.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f825fd5f617d0..038875326577e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -954,6 +954,7 @@ namespace ts { const reverseMappedCache = new Map(); let inInferTypeForHomomorphicMappedType = false; let ambientModulesCache: Symbol[] | undefined; + let 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. @@ -45662,17 +45663,29 @@ namespace ts { } function getAmbientModules(sourceFile?: SourceFile): Symbol[] { - if (!ambientModulesCache) { - ambientModulesCache = []; - const envGlobals = denoContext.hasNodeSourceFile(sourceFile) ? denoContext.combinedGlobals : globals; + 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 { From c32df33229d303fde49662a15897c022a2a4a29d Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 6 Oct 2022 10:00:13 -0400 Subject: [PATCH 08/15] Fix ambient module lookup. --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 038875326577e..fc322c70b34d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12898,7 +12898,7 @@ namespace ts { 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; } @@ -29724,7 +29724,7 @@ namespace ts { // 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) From bc6809557232a5f83d06d35fbaf4df1b02f1d1fa Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 6 Oct 2022 11:39:01 -0400 Subject: [PATCH 09/15] Keep these in the node code. --- src/compiler/deno.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index 66e2625601327..12dde13112f39 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -37,6 +37,19 @@ namespace ts.deno { "setInterval", "setImmediate", "Global", + "AbortController", + "AbortSignal", + "Blob", + "BroadcastChannel", + "MessageChannel", + "MessagePort", + "Event", + "EventTarget", + "performance", + "TextDecoder", + "TextEncoder", + "URL", + "URLSearchParams", ]) as Set; export function createDenoForkContext({ From b9ceae0b7dc72c05a8059c437b7d2d3389755773 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 7 Oct 2022 14:39:29 -0400 Subject: [PATCH 10/15] Resolve npm package references to the ambient module when not found. --- src/compiler/checker.ts | 19 +++++++++++++++++++ src/compiler/deno.ts | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fc322c70b34d8..6273cb2e61174 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3594,6 +3594,25 @@ namespace ts { } 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/"); diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index 12dde13112f39..69207b53be268 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -174,4 +174,43 @@ namespace ts.deno { } } } + + 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:/, ""); + 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); + } + return { + name: nameParts.join("/"), + versionReq, + subPath: parts.length > nameParts.length ? parts.slice(nameParts.length).join("/") : undefined, + }; + } } From 8e8c37b4e1666222a75e675c91326a1d2fb0f199 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 17 Oct 2022 14:03:37 -0400 Subject: [PATCH 11/15] Update. --- src/compiler/checker.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6273cb2e61174..1491bc0ef3414 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3924,8 +3924,14 @@ namespace ts { if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; if (symbol.members) result.members = new Map(symbol.members); if (symbol.exports) result.exports = new Map(symbol.exports); - const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above - result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + // deno: temp change until https://github.com/microsoft/TypeScript/pull/51136/files is merged + if (moduleType.flags & TypeFlags.StructuredType) { + const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + } + else { + result.type = moduleType; + } return result; } From 962fba0ce8fa4f6547a655063e288f01cf8fcc55 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 17 Oct 2022 17:37:55 -0400 Subject: [PATCH 12/15] Add manual changes already made in deno repo --- src/compiler/commandLineParser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 370461abd8559..7632c3c142d88 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -38,6 +38,8 @@ namespace ts { // 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"], @@ -85,7 +87,7 @@ namespace ts { ["es2022.object", "lib.es2022.object.d.ts"], ["es2022.sharedmemory", "lib.es2022.sharedmemory.d.ts"], ["es2022.string", "lib.es2022.string.d.ts"], - ["esnext.array", "lib.es2022.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"], From b0a7e5e72f2d6fed99e140ce1a73cb129353d9d8 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 21 Oct 2022 10:50:25 -0400 Subject: [PATCH 13/15] Revert pr change --- src/compiler/checker.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1491bc0ef3414..6273cb2e61174 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3924,14 +3924,8 @@ namespace ts { if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; if (symbol.members) result.members = new Map(symbol.members); if (symbol.exports) result.exports = new Map(symbol.exports); - // deno: temp change until https://github.com/microsoft/TypeScript/pull/51136/files is merged - if (moduleType.flags & TypeFlags.StructuredType) { - const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above - result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); - } - else { - result.type = moduleType; - } + const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); return result; } From 3ed9eeec6800e27cced6ec02fd471530470738b3 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 26 Oct 2022 20:14:22 -0400 Subject: [PATCH 14/15] Allow synthetic default imports in esm --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6273cb2e61174..d34325eabd6a9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2779,7 +2779,7 @@ namespace ts { const usageMode = file && getUsageModeForExpression(usage); if (file && usageMode !== undefined) { const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); - if (usageMode === ModuleKind.ESNext || result) { + if (result) { return result; } // fallthrough on cjs usages so we imply defaults for interop'd imports, too From d56d1e156a3055dec315e5bc8a33df96b572d4d3 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 21 Nov 2022 20:17:25 -0500 Subject: [PATCH 15/15] Support npm package references with leading `/` --- src/compiler/deno.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/deno.ts b/src/compiler/deno.ts index 69207b53be268..1554ac299b872 100644 --- a/src/compiler/deno.ts +++ b/src/compiler/deno.ts @@ -193,7 +193,7 @@ namespace ts.deno { if (!text.startsWith("npm:")) { throw new Error(`Not an npm specifier: ${text}`); } - text = text.replace(/^npm:/, ""); + text = text.replace(/^npm:\/?/, ""); const parts = text.split("/"); const namePartLen = text.startsWith("@") ? 2 : 1; if (parts.length < namePartLen) { @@ -207,8 +207,12 @@ namespace ts.deno { 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: nameParts.join("/"), + name, versionReq, subPath: parts.length > nameParts.length ? parts.slice(nameParts.length).join("/") : undefined, };