Acknowledgement
Comment
What happens
getImmediateAliasedSymbol for a namespace import never gets back to the file target symbol if the module has a default export.
So for example for:
// mod.ts
export default class User {}
/// usage.ts
import * as X from "./mod"
If we have:
const moduleSymbol = checker.getSymbolOfSourceFile("./mod.ts")!;
let ns = /* X from `import * as X from "./mod"` */
const aliasSymbol = checker.getSymbolAtLocation(ns)!;
const resolvedModuleSymbol = checker.getImmediateAliasedSymbol(aliasSymbol);
console.log(moduleSymbol === resolvedModuleSymbol)
This will log true in TS6, false in TS7.
It will also log true in TS7 if mod.ts does not have a default export. (delete the default)
In TS7 when the module has a default export we seem to get a cloned symbol. This does internally have a target, but that is not exposed in the API so we can't get from the import symbol back to the same symbol as we would get by getting the symbol of the file.
Caused by microsoft/typescript-go#2168. which dropped the dontRecursivelyResolve flag that let classic tsc skip synthetic-default-interop cloning for this one-hop lookup.
Possible solutions:
getImmediateAliasedSymbol returns the same symbol as it did in TS6
- Expose the
target property on Symbol. Although I am not sure how this would interfere with other symbols that have a target.
- Expose some other way to reliably get the file symbol starting from the namespace import symbol.
Reproduction Code
TS6 (classic tsc):
import * as ts from "typescript";
const indexPath = "/repro/index.d.ts";
const withDefaultPath = "/repro/with-default.d.ts";
const files = new Map([
[indexPath, `import * as X from "./with-default";`],
[withDefaultPath, `export default class User {}`],
]);
const host = ts.createCompilerHost({});
host.readFile = fileName => files.get(fileName);
host.fileExists = fileName => files.has(fileName);
host.directoryExists = dirName => dirName === "/repro";
console.log(`=== TypeScript ${ts.version} (classic) ===`);
const program = ts.createProgram([indexPath, withDefaultPath], { strict: true }, host);
const checker = program.getTypeChecker();
const indexFile = program.getSourceFile(indexPath)!;
const withDefaultFile = program.getSourceFile(withDefaultPath)!;
const moduleSymbol = checker.getSymbolAtLocation(withDefaultFile)!;
let ns = ((indexFile.statements[0] as ts.ImportDeclaration).importClause?.namedBindings as ts.NamespaceImport)?.name
const aliasSymbol = checker.getSymbolAtLocation(ns!)!;
const resolvedModuleSymbol = checker.getImmediateAliasedSymbol(aliasSymbol);
console.log("moduleSymbol === getImmediateAliasedSymbol(X)?", moduleSymbol === resolvedModuleSymbol);
TS7 (tsgo):
import { createFileSystemLayer } from "@typescript/typescript/unstable/fs";
import { API } from "@typescript/typescript/unstable/sync";
import type { ImportDeclaration, NamespaceImport } from "@typescript/typescript/unstable/ast";
const cwd = "/repro";
const indexPath = "/repro/index.d.ts";
const withDefaultPath = "/repro/with-default.d.ts";
const configPath = "/repro/tsconfig.json";
const files = {
[indexPath]: `import * as X from "./with-default";`,
[withDefaultPath]:`export default class User {}`,
[configPath]: JSON.stringify({ compilerOptions: { strict: true, noEmit: true }, files: [indexPath, withDefaultPath] }),
};
console.log("=== tsgo ===");
const api = new API({ cwd });
const snapshot = api.createSnapshot({ openProjects: [configPath], fileSystem: createFileSystemLayer(Object.entries(files))});
const project = snapshot.getConfiguredProject(configPath)!;
const checker = project.checker;
const indexFile = project.program.getSourceFile(indexPath)!;
const moduleSymbol = checker.getSymbolOfSourceFile(withDefaultPath)!;
let ns = ((indexFile.statements[0] as ImportDeclaration).importClause?.namedBindings as NamespaceImport)?.name
const aliasSymbol = checker.getSymbolAtLocation(ns)!;
const resolvedModuleSymbol = checker.getImmediateAliasedSymbol(aliasSymbol);
console.log("moduleSymbol === getImmediateAliasedSymbol(X)?", moduleSymbol === resolvedModuleSymbol);
api.close();
Acknowledgement
Comment
What happens
getImmediateAliasedSymbolfor a namespace import never gets back to the file target symbol if the module has a default export.So for example for:
If we have:
This will log
truein TS6,falsein TS7.It will also log
truein TS7 ifmod.tsdoes not have a default export. (delete thedefault)In TS7 when the module has a default export we seem to get a cloned symbol. This does internally have a target, but that is not exposed in the API so we can't get from the import symbol back to the same symbol as we would get by getting the symbol of the file.
Caused by microsoft/typescript-go#2168. which dropped the
dontRecursivelyResolveflag that let classictscskip synthetic-default-interop cloning for this one-hop lookup.Possible solutions:
getImmediateAliasedSymbolreturns the same symbol as it did in TS6targetproperty on Symbol. Although I am not sure how this would interfere with other symbols that have a target.Reproduction Code
TS6 (classic tsc):
TS7 (tsgo):