-
Notifications
You must be signed in to change notification settings - Fork 12.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixing namespace import debug failure #59004
base: main
Are you sure you want to change the base?
Conversation
…rrorForNamespaceImport
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the identifier for the import did not have a parent
This isn’t quite right—the symbol the identifier for the import resolved to doesn’t have a parent, because the symbol is the module symbol, not an export of a module. That’s what namespace imports are—imports that bind an identifier to an entire module namespace record. So while your draft implementation looks pretty good, there are two problems: (1) addImportFromExportedSymbol
is not the right function to contain it, since the symbol being passed in is not an export, and (2) there are other ways an import/require can bind to a module symbol that are still broken. For example, I just changed in pasteEdits_namespaceImport.ts
- import * as test from "./a";
+ import test = require("./a");
and replicated the same crash. The same thing can also happen with a JavaScript require, as well as with a default import under certain conditions.1 The detection you need to determine if the symbol you have is for a module symbol is the isExternalModuleSymbol
function instead of relying on looking at the syntax of referenceImport
. However, I think you should maybe move your new code into a new ImportAdder function instead of expanding addImportForExportedSymbol
.
Footnotes
-
↩
Test case
/// <reference path="../fourslash.ts" /> // @Filename: /folder/c.ts //// [||] // @Filename: /a.ts //// const abc = 10; //// const def = 20; //// export interface testInterface { //// abc: number; //// def: number; //// } // @Filename: /b.mts //// import test from "./a.js"; //// //// [|function foo(abc: test.testInterface, def: test.testInterface) { //// console.log(abc); //// console.log(def); //// }|] //// // @Filename: /tsconfig.json ////{ "compilerOptions": { "module": "nodenext" }, "files": ["/folder/c.ts", "a.ts", "b.mts"] } const range = test.ranges(); verify.pasteEdits({ args: { pastedText: [`function foo(abc: test.abc, def: test.def) { console.log(abc); console.log(def); }`], pasteLocations: [range[0]], copiedFrom: { file: "b.mts", range: [range[1]] }, }, newFileContents: { "/folder/c.ts": `import * as test from "../a"; function foo(abc: test.abc, def: test.def) { console.log(abc); console.log(def); }` } });
for (const e of declaration.importClause.namedBindings.elements) { | ||
const symbol = checker.getSymbolAtLocation(e.propertyName || e.name); | ||
if (isNamespaceImport(declaration.importClause.namedBindings)) { | ||
const symbol = checker.getSymbolAtLocation(declaration.importClause.namedBindings.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there’s no need for checker.getSymbolAtLocation
anywhere in this function. Declarations always have a .symbol
; you can just do declaration.importClause.namedBindings.symbol
here, and .symbol
on each ImportSpecifier
below. Even the existing variable declaration / binding pattern stuff further down can lose the getSymbolAtLocation
.
@@ -356,6 +357,24 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog | |||
} | |||
} | |||
|
|||
function addImportForExternalModuleSymbol(symbol: Symbol, originalSymbol: Symbol, isValidTypeOnlyUseSite: boolean) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
symbol
/originalSymbol
are confusing names here. It looks like originalSymbol
is the actual module symbol, and symbol
is just an existing alias for the module that’s only being used to see what name to use. Instead, try just passing in the module symbol and the name you want to use?
@@ -356,6 +357,24 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog | |||
} | |||
} | |||
|
|||
function addImportForExternalModuleSymbol(symbol: Symbol, originalSymbol: Symbol, isValidTypeOnlyUseSite: boolean) { | |||
const useRequire = shouldUseRequire(sourceFile, program); | |||
if (originalSymbol.valueDeclaration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this check for? When does an external module symbol not have a valueDeclaration?
createModuleSpecifierResolutionHost(program, host), | ||
); | ||
const info: FixInfo = { | ||
fix: { kind: ImportFixKind.AddNew, importKind: ImportKind.Namespace, addAsTypeOnly: isValidTypeOnlyUseSite ? AddAsTypeOnly.Allowed : AddAsTypeOnly.NotAllowed, useRequire, moduleSpecifierKind: undefined, moduleSpecifier }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there may be multiple different forms of import that can reference a whole module, you were asking about whether it’s ok to unconditionally coerce them into a namespace import. That might be fine, but it seems like it would make sense for this function to take an optional referenceImport
like addImportFromExportedSymbol
, and copy its kind and type-only status where appropriate.
Fixes #58740
This debug failure happened when trying to resolve a namespace import. As the identifier for the import did not have a parent, an error was thrown.